Repository navigation
Sound, and video with it, on X11 and Wayland; flicker, latency and what a
session used, measured; a session's own clock, identity and network;
timed scripts of input; and checks that a picture comes out the same
twice. Pictures of GTK 4 programs change: see the first note below.
Changes to check when upgrading
- GTK 4 draws text with manual font rendering (GTK 4.16 and later),
so text is drawn at its color rather than fainter, by however much
automatic rendering draws it on that GTK release. Every GTK 4 picture
with text changes, its widths too, so layout can shift, anda11y contrastmeasures thin text higher: take committed GTK 4 pictures
again. A seeded home whose GTK 4settings.inisets
gtk-font-renderingkeeps its own. - Under bubblewrap, a program can't gain privileges, its supplementary
groups show asnogroup, and one killed by a signal exits with 128
plus the signal's number.--no-sandboxruns it as before. comparesays where pixels differ, after how many:N pixels differ, in WxH at X,Y, on each picture's line with directories too.- A session with a display wants ImageMagick, and one with X11 or
Xwaylandxwininfoandxprop(x11-utils), before it starts, rather
than at its first wait or picture. Screen.recordreturns a Recording, not the path: itspath, and
with sound what was heard. It stands in for the path where one is
opened (os.fspath) and instr(), but.suffixand the rest of a
Path's are on.path.
Changed
- Waits look faster.
wait stable,matchandchangetake their
pictures straight from the X server (a few milliseconds, whereimport
took tens) or from grim's raw output on Wayland, and compare them in
memory, with no PNG between;matchandchangelook every 10 ms. A
change is seen within about a frame, and the timechangesays is that
close. - Every program a session spawns runs under bubblewrap where it can,
not only with--gamepads:/dev/sndand/run/user/UIDare empty
for it, so it can't play through the machine's speakers or find the
desktop's Wayland, PulseAudio, PipeWire or bus sockets. Without
bubblewrap, the session says so and runs it as before. - A window asked for and not there is said with the titles, or app ids,
there are, and that it's matched in full. - The README is a front page, the reference moved to docs/commands.md,
sealed.md, gamepads.md, python.md and node.md, the a11y commands to
docs/accessibility.md, the Wayland example to docs/wayland.md, and the
tests to CONTRIBUTING.md.
Added
shotbox play SCRIPT: a script of timed input, played in a
session on one clock, with no command started for each step, so a demo
plays out the same way each time it's recorded. A line is a time
(seconds from the start, or+SECSafter the step before) and a
command for inside a session; one ending in&runs on in the
background, a recording say, and${NAME}is the session's variable.
Each line's options are checked before the first runs, and a failure
says its file and line. AlsoScreen.play, which takes the text, or a
Path to the file, andvarsfor${NAME}.wait line RE FILE: wait for a line matching RE to be written to
a file, and print it, for a script that follows a program's log of
what happens rather than sleeping by a clock the program may not
keep to. From the file's end when the wait starts (--from-startfor
the top); a file not there yet is waited for, and one rotated or
rewritten is followed. AlsoScreen.wait_line, and MCP'swait_for,
whose file is in the--allowfile's directory.shoot --twice(andterm --shoot --twice): the picture taken in
two fresh sessions, and a failure if they differ, beyond--fuzzand
--max, saying how many pixels and the box they're in, with
OUT-diff.pngbeside OUT. If they're alike, one is kept as OUT.--identity USER@HOST: who and where a session's programs are,
forrun,shoot,term,start,exec,Session(identity=...)
and MCP'sstart_session:hostname,uname -n,whoami,id -un,getpwuid(),ls -l's owner,$USER,$LOGNAMEand
$HOSTNAMEsay USER and HOST, so a picture doesn't show who took it.
It needs bubblewrap, and not--no-sandbox.--clock TIMEand--tz ZONE: every program a session runs sees
the wall clock start at TIME (ISO 8601, in ZONE or UTC) and run on, a
program spawned later reading the same clock as the first, so dates in
pictures don't change between shoots. Forrun,shoot,term,
start,exec,Session(clock=..., tz=...)and MCP's
start_session; by libfaketime, which doesn't reach static binaries or
Go programs.- What a command needs, checked before it starts anything. A tool
not on PATH,/dev/uinputit can't open for--gamepads, or PyGObject
with VTE (term) or AT-SPI (--a11y) that this Python can't load is
said at once in a line, with the package to install, or, from a
virtualenv that can't see the system's packages, how to make one that
can (for pipx's,pipx install --force --system-site-packages shotbox);Sessionand MCP'sstart_sessionsay the same.
shotbox doctorgoes through every check, a line each, and fails
only if what every session needs is missing. --usage [FILE]: what a session's programs used, together, and
apart, its own processes: peak memory and CPU time, from a systemd
slice of the session's where the user's systemd can make one, else
read from/proc; as JSON to FILE.Session(usage=True)and MCP's
start_sessiontake it too.shotbox latency move X1 Y1 X2 Y2 --watch G: how long what's
drawn takes to follow the pointer, a move at a time, as the median and
95th percentile;Screen.latencyfrom Python.shotbox flicker: a part of the screen that keeps switching
between two looks, or flashes another once, found from pictures taken
as fast as they come, a tile at a time, by its mean color and in time:
its box, how many changes, how often, and both colors; and how many
pictures it took and how many changed, on stdout whatever's found, a
rate too slow said. With--save DIR, a sheet of each one's pictures,
its box outlined and a tile round it magnified, each labeled with when
it showed.
Screen.flickerfrom Python.--net loopback: a network of the session's own, which every
program it runs shares and nothing else is on, forrun,shoot,
start,execand MCP'sstart_session: a server a test starts is
reached by the rest of the session, and from nowhere else.wait port
looks in it.shotbox listen IN.wav: a recording's peak and RMS level, samples
clipped, and gaps, where a program fell behind and left the sink
silent; it fails on any, or on silence.shotbox.sound.hearfrom
Python, and MCP'ssoundtool, which records a session's sound
betweenstartandstopand says the same of it.- A GitHub Action,
uses: mishan/shotbox@vX.Y.Z: installs shotbox
at that version and what it runs on an Ubuntu runner, with each
feature (a11y,wayland,audio,record,term,gamepads)
asked for by name, and lets bubblewrap make its sandbox. shotbox exec -- COMMAND: the session's seal with no display, for
a headless browser or a test suite: its own environment, home and bus,
under bubblewrap, and--audioif it's wanted.--no-sandbox, for a program that needs a setuid helper (sudo, an
AppImage), which can't run under bubblewrap.- What's sealed, in docs/sealed.md: what a session keeps from a program,
and what it doesn't. And, in the README, instructions to give an agent
so it runs graphical programs, browsers and sound in a session. wait match REF.pngandwait change, with--window,--crop,
--fuzzand--max: wait until the screen looks like a picture, or
looks different from when the wait started, and say how long it took.
AlsoScreen.wait_matchandwait_change, and MCP'swait_for,
whose picture to match is a file in the--allowfile's directory.shotbox record OUT.wav: what plays in a session with--audio,
48 kHz 16-bit stereo, bit for bit for what plays in that format, on X11
or Wayland, for--seconds(to the sample) or until stopped.- Recipes, in docs/recipes.md: checking a change in a GUI, visual
regression in CI, an accessibility audit, a game smoke test and an
agent over MCP. - How shotbox compares, in docs/comparison.md, with
linux-x11-harness, agent-workspace-linux, kwin-mcp, agent-desktop,
dogtail, openQA and others. shotbox record OUT.mp4in Wayland sessions, and .webm and .gif:
the same options, the same length for--seconds, and the same ways
to stop, the file finished however it stops. wf-recorder takes the
frames over wlroots' screencopy and ffmpeg encodes them, as on X11.
sway draws no pointer, so--pointeris refused there. The Action's
recordinstalls wf-recorder whenwaylandis on too.shotbox record OUT.mp4 --sound, and .webm: in a session with
--audio, what plays too, in the same file, AAC in an MP4, Opus in a
WebM, by one ffmpeg, the sound in step with the picture from its first
frame, within a frame or so. Both are--secondslong, and it stops
the waysrecorddoes, the file finished. Then it says whatlisten
would of the sound: its level, clipping and dropouts, bylisten's
--gap,--clippedand--quiet, failing for them with--strict.
AlsoScreen.record(..., sound=True), whose Recording has what
shotbox.sound.hearheard, and itsproblems().- MCP's
recordtool: a clip of the screen or a window, up to 60
seconds and 100 MB, saved as an .mp4 in the session's scratch
directory or as a new file in the--allowfile's directory. It
answers with the path, the clip's length and a sheet of up to 8
frames from it with their times, as an image, since a model can look
at frames but not play a file; withsound, in a session with
audio, what was heard, as thesoundtool says it.
Screen.record(..., size=N)ends a recording at N bytes.
Fixed
term --shootpasses on--net,--usageand--no-sandbox,
which it took and then left out. It now gives shoot every session
option as it was given, read back through its own options, so one
added later reaches shoot too.shootmakes OUT's directory if it isn't there, where it failed
with a traceback.- A recording's keeper killed outright takes ffmpeg (and
wf-recorder) with it, ffmpeg finishing the file, rather than leaving
them recording after the session has gone.