Repository navigation
🎥 Record a region to WebM, stopped by the same key - #85
Merged
Merged
Conversation
added 4 commits
September 17, 2026 11:07
--record marks out an area with the overlay -- the region tool only, since nothing drawn on a frozen frame could survive into a video of the live screen -- and hands it to ffmpeg. The command exits at once, printing the path it is filling, so a shell pipeline is not held open for the length of a recording and one hotkey can do both ends of it. Stopping is the same command again: the running ffmpeg is remembered in state.json and sent SIGINT, which is how it is asked to finalise a file rather than leave it truncated. The notification carries a Stop button that takes the same path. A pid left behind by a killed agent is checked against /proc before anything is signalled, so a recycled number cannot be aimed at somebody else's process. The file is created at 0600 before ffmpeg opens it: ffmpeg truncates the name it is given, and a file it made itself would sit at the umask's mode for the whole recording, holding a video of somebody's screen. --gif converts afterwards, through a palette built from the recording's own colours, and removes the WebM. X11 only. Under Wayland the one route is the portal's screencast, which cannot begin without somebody choosing a screen in a dialog, so a hotkey cannot start one; it refuses with a message instead of half working. ffmpeg joins Recommends: without it, one clear error naming the package. The naming rule a PNG already used moves to output.destination(), so a recording lands the same way -- -o, then -d, then the timestamp.
A red record dot beside Capture, on the bar and on the floating palette both -- they are meant to be the same controls in two shapes, so putting it on one and not the other would be the odd thing. Mark out an area, press the dot instead of Capture, and the screenshot session becomes a recording without having to start again from a different command. It is laid out only where recording could actually work: with ffmpeg installed, under X11. A button that cannot do anything is worse than no button, and cli already works out the answer before the overlay is built. Marks on the scene are left behind. A recording is of the live screen, and they were made on a frozen frame of it -- they would be a still picture pasted over moving video, describing a moment that has already gone. The overlay now answers with an Outcome: a picture to deliver, or an area to record. --record and the button meet at the same two lines of cli, so there is one way a recording starts rather than two.
A recording's only control was a button on a notification, and GNOME collapses notifications that carry buttons -- so the one way to stop a recording could be behind an expander arrow in a tray nobody has open. That is a poor place for the only control a running thing has. So while a recording runs there is a red dot in the status area, beside the volume and the battery, carrying one item: Stop recording. That is where somebody looks for something that is currently happening, and it is where the desktop's own recorder puts it. The agent that owns ffmpeg already holds a main loop for the length of the recording, so it owns the indicator too and the dot goes away with it. gir1.2-ayatanaappindicator3-0.1 joins Recommends rather than Depends: the typelib may not be installed, and the desktop may have nothing listening for status notifier items at all (on GNOME that is the AppIndicator extension, which Ubuntu ships switched on). Either way indicator() returns None, the notification's Stop and running the command again both still work, and nothing refuses to record over a missing banner.
The app-indicator typelib was a wrapper round a D-Bus interface and one more thing to have installed -- and a Recommends that somebody skips would mean the only control a running recording has quietly does not appear. Gio is already a hard dependency, so the item and its menu are served from here: seven properties, a menu that never changes, and a click. The menu is not decoration. GNOME's AppIndicator extension opens the item's menu on a single click and only calls Activate on a double one (indicatorStatusIcon.js), so an item with no menu looks broken to anybody who clicks it once. Activate and the middle click stop the recording too, for a desktop that reads them that way. Registering with the watcher is asynchronous on purpose. The watcher reads the item's properties back before it answers, and a blocking call cannot serve them: the reply waits on the thread that is already waiting, and it deadlocks until the timeout with nothing on the bar. Sent asynchronously it is answered from the loop the recording already runs under. Checked against the running shell: it registers, serves what the panel reads, and a dbusmenu click -- the same call the panel makes -- stops the recording. The suite covers the answers themselves, no session bus needed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
--recordopens the overlay with the region tool only; Capture (or Enter) starts recording instead of taking a shot. The command exits at once and prints the path it is filling, so$(programmers-screenshot --record)does not block for the length of the recording.state.jsonand getsSIGINT, which finalises the file rather than truncating it. One hotkey does both ends. The notification that sits there while it records carries a Stop button taking the same path./proc/<pid>/commbefore anything is signalled, so a recycled pid can never be aimed at an unrelated process.--gifconverts afterwards through a palette built from the recording's own frames, and removes the WebM.ffmpegjoinsRecommends, and its absence is one clear error naming the package.output.destination(), so a recording lands the same way:-o, then-d, then a timestamp beside the screenshots.Validation
python3 tests/run.py— 25 suites, 0 failures.ruff check src tests(0.16.6, the CI pin) — passed../build.sh— package builds.tests/test_recording.py: the x11grab command line (size, offset,$DISPLAY, framerate, realtime VP9), the logical→physical scale, odd sizes rounded down to even (yuv420p refuses an odd width), where the file lands, 0600 on a fresh and a pre-existing file, the refusals, and the toggle driven against a real child process — pid found,SIGINTsent, process gone, state cleared, and a foreign pid left untouched.tests/test_interaction.py: the region-only overlay hands back the rectangle rather than a picture, and Escape starts nothing.Manual check (the recording itself), on X11, ffmpeg 6.1.1
SIGINT: playablevp9/matroska,webm, 16.88s,-rw-------.XDG_CONFIG_HOME:--recordwhile recording exited 0,state.jsonwent back to{"recording": null}, the file was a 400×300 VP9 WebM of 9.16s at 0600, and no ffmpeg was left behind.--gif: 240×160 GIF, 227 frames, 0600, with the intermediate WebM removed.Not in scope
Audio, a webcam overlay, trimming, annotating a recording, any upload — as the issue says. A recipe still cannot record:
--recordis for the person at the keyboard, and--recipe-helpand the installed skill now say so.Closes #78