eensh — the screenshot tool for agents

Capture an X11 desktop, region, or window as PNG or JPEG — to a file, to stdout, or inline as base64 in the response. Compare two frames to see what changed. Wait for the screen to start or stop changing before capturing. Keep a display session open across calls, so later captures read from memory instead of the screen.

bash
eensh capture --display :1 --region 0,0,1280,720 \
  --width 640 --format jpeg --quality 75 \
  --base64 --json
A region of the screen selected, and the returned image beside it1280×720scale_x=2.0640×360, in the responsesource — 1920×1080image — what you get back
The region is 1280×720 at source (0, 0). The returned image is 640×360. Both sizes are in the response, and the transform maps image coordinates back to source.

That one command writes a single JSON document to stdout: the region it came from, the size of the image, the transform between the two, and where the time went — with the image carried inside it. There is no file to read back and no second request.

The three questions that come next are the ones the rest of eensh exists to answer: what changed, has it stopped, and what did it look like along the way?

A capture, in one command

A 1280×720 region of the display, resized to 640 wide, JPEG, and the image embedded as base64 — so the response is self-contained. Add a filename and the same command writes the file instead.

bash
eensh capture --display :1 \
  --region 0,0,1280,720 \
  --width 640 --format jpeg --quality 75 \
  --base64 --json

What it prints

stdout
{
  "source":  { "kind": "region", "display": ":1",
               "x": 0, "y": 0,
               "width": 1280, "height": 720 },
  "image":   { "width": 640, "height": 360,
               "format": "jpeg", "quality": 75,
               "media_type": "image/jpeg",
               "byte_length": 24118,
               "encoding": "base64",
               "data": "/9j/4AAQSkZJRgABAQ..." },
  "transform": { "origin": "top-left",
                 "offset_x": 0, "offset_y": 0,
                 "scale_x": 2.0, "scale_y": 2.0 },
  "timing":  { "capture_us": 4210, "resize_us": 1180,
               "encode_us": 9640, "base64_us": 210,
               "total_us": 15240 }
}

One response, and data is the image. Nothing to fetch afterwards.

Or write it instead

bash
eensh capture --display :1 \
  --region 0,0,1280,720 \
  --width 640 --format jpeg --quality 75 \
  --json \
  region.jpg          # the output path: positional, no leading --
ls -la region.jpg
-rw-r--r-- 1 you you 24118 Oct 5 19:04 region.jpg
file region.jpg
region.jpg: JPEG image data, 640x360, baseline

region.jpg is the file that appears — and it appears only because it was named. The output path is the one argument with no -- in front of it. Drop it and nothing is written; the bytes go to stdout instead.

Where each thing goes

Image bytes and JSON never share a stream, so there is never a document to parse out of the middle of binary. Which stream you get depends on two flags and on whether you named a file:

stdout

The JSON document, because --json was passed. Without it, stdout carries the raw encoded image instead — which is why the two are never mixed.

stderr

Errors, and anything you asked to be logged — --time writes its per-stage timings here. It also takes the JSON in the one case where stdout is already full of raw bytes: --json without --base64 and without a file.

a file

Only if you name one. The two commands above differ in exactly one respect: the second ends in region.jpg.

Either way the region is 1280 wide and the image is 640. Both sizes are in the response, and the transform says how to get from one to the other — so a click on the image can be translated to a click on the screen without guessing which number meant what.

eensh feature areas

All five are built on one primitive: a capture produces a raw frame, and nothing is encoded until a caller asks for a particular size and format. That is why the areas combine, and why adding one never changed how a frame is captured. presentation is the sixth area — it decides the size, format, and quality of what is sent.

capture
Capture a desktop, a region, or a window
One command writes an image and a JSON document describing it.

Source rectangle, image size, and timings.

Your first capture
encode
Encode as PNG or JPEG
To a file, to stdout, or inline as base64 inside the JSON response.

One call returns the pixels and the description.

Reading the response
compare
Compare two frames
Changed-pixel count, changed fraction, and the bounding box of what moved.

Or the box as an image, cropped from the newer frame.

Comparing two moments
observe
Snap after the screen starts or stops changing
wait-change returns when it updates; wait-stable once it holds still.

Replaces a fixed sleep and a guess.

Replacing sleep()
session
Keep a display session open
A background service holds the connection and recent raw frames.

Later captures and comparisons read from memory.

Persistent sessions