Skip to content

Add a "Screenshot Output" Hotkey That Saves the Program Output as a PNG - #6231

Open
au-sf wants to merge 6 commits into
stagingfrom
1216010475168255-screenshot-output-hotkey
Open

au-sf wants to merge 6 commits into
stagingfrom
1216010475168255-screenshot-output-hotkey

Conversation

@au-sf

@au-sf au-sf commented Sep 26, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Adds the hotkey OBS Studio has had for years: press a key, get a full-resolution PNG of what is being streamed or recorded, dropped into the recording folder. The motivating use is grabbing a thumbnail candidate during a session without leaving the app or scrubbing a recording afterwards. Behaviour is copied from OBS deliberately, so anyone coming from OBS finds the same file in the same place with the same name.

Depends on streamlabs/obs-studio-node#1786 (streamlabs/obs-studio-node#1786), which has shipped in obs-studio-node 0.27.19; staging (merged into this branch) already pins that version in scripts/repositories.json. The desktop app cannot read pixels off the canvas itself: the editor preview is a native child window drawn by the OBS backend, and obs-studio-node has never exposed a readback call. That PR adds one native function; this PR is the thin frontend on top of it. On an older backend without the function, pressing the key shows a "needs a newer backend" warning and does nothing else.

Fixes

app/services/screenshot.ts (new, ScreenshotService). takeScreenshot(display = 'horizontal') gathers the same inputs OBS uses and hands them to the backend in one synchronous call, NodeObs.OBS_content_takeScreenshot(video, directory, filenameFormat, noSpace): the horizontal video context from VideoSettingsService.contexts, the recording folder from OutputSettingsService.getRecordingSettings(display).path (mode-aware, unlike SettingsService.recPath, which only reads the Advanced key), the Advanced → Recording → FilenameFormatting pattern (defaulting to OBS's %CCYY-%MM-%DD %hh-%mm-%ss), and the mode-specific "file name without space" flag, read with the same lookup OutputSettingsService already uses. The backend renders the program mix at canvas resolution and writes Screenshot <formatted>.png, adding (2), (3)… if the name is taken. On success the service pushes a SUCCESS notification, "Screenshot saved as ", whose click action calls showScreenshot(path), which reveals the file with shell.showItemInFolder, and records ScreenshotOutput feature usage. A missing recording folder, a missing video context, an older backend without the function, or a backend error each produce a WARNING notification instead of a crash.

app/services/hotkeys.ts. New general action SCREENSHOT_OUTPUT (label "Screenshot Output"), placed after SPLIT_FILE so it sits next to the other output-related hotkeys in Settings → Hotkeys. The action name is permanent: saved bindings are keyed by it (see the warning above getMigrationMapping).

app/app-services.ts. Registers ScreenshotService. app/i18n/en-US/hotkeys.json. The label and the four notification strings. test/regular/settings/hotkeys.ts. "Screenshot Output" added to the list of hotkeys the settings test expects to find.

Files changed: app/services/screenshot.ts, app/services/hotkeys.ts, app/app-services.ts, app/i18n/en-US/hotkeys.json, test/regular/settings/hotkeys.ts

Performance Implications

None on any existing path: nothing runs until the hotkey is pressed. A press is one synchronous backend call that renders one frame, reads it back from the GPU and encodes a PNG; on a 1080p canvas that is roughly 100–300 ms during which the worker window is blocked, comparable to other synchronous NodeObs calls the app already makes. There is no polling, no new state, and the file write happens in the backend process.

Verification

  • yarn typecheck and yarn eslint on the changed files: clean. In test/regular/settings/hotkeys.ts, "Populates essential hotkeys" passes with the new label in the list. "Binds a hotkey" fails on this Mac at its post-restart assertion, and fails identically on the parent commit (52638c543) with the same bundle and harness, so it is pre-existing and unrelated.
  • OSN side: the native function's own tests pass (five cases: file written at canvas size, (2) de-duplication, _ substitution with noSpace, missing directory throws, non-video argument throws).
  • End to end in the real app (macOS arm64, Electron 29.3.1), with the locally built obs-studio-node from the companion branch dropped into node_modules: a throwaway e2e probe added a Color Block source, called ScreenshotService.takeScreenshot() twice (the hotkey's exact code path), and confirmed Screenshot 2026-09-25 16-52-24.png and …16-52-25.png in the recording folder, each a valid 1280×720 PNG with the block visible (17% non-black pixels), the footer toast "Screenshot saved as …", and both entries listed as clickable rows in the Notifications window. The probe is not part of this PR.
  • With the stock backend (no OBS_content_takeScreenshot), the same probe confirmed the key press produces only the "needs a newer backend" warning and the app keeps running.

Notes

  • Only the horizontal canvas is captured. In dual-output mode the vertical canvas is a natural follow-up (a second hotkey or a display argument); the service already takes a display parameter so that is a one-line change once the product decision is made.
  • The screenshot captures the program output (what viewers see), not the preview, matching OBS's "Screenshot Output" rather than "Screenshot Preview".

Asana Link

https://app.asana.com/0/1207748235152481/1216010475168255

… a PNG [Asana: 1216010475168255]

Mirrors OBS Studio's "Screenshot Output": one key press writes
"Screenshot <Filename Formatting>.png" at canvas resolution into the
recording folder and shows a notification that reveals the file.

The capture itself is the new OBS_content_takeScreenshot in
obs-studio-node (branch screenshot-output); this adds the ScreenshotService
that gathers the recording folder, filename format and no-space flag the
same way OutputSettingsService does, the SCREENSHOT_OUTPUT general hotkey,
the strings, and the label in the hotkeys settings test. On a backend
without the function the key shows a warning instead.
@bundlemon

bundlemon Bot commented Sep 26, 2026 •

Copy link
Copy Markdown

BundleMon

Files added (4)
Status Path Size Limits
✅ renderer.(hash).js
+10.9MB -
✅ vendors~renderer.(hash).js
+4.67MB -
✅ updater.js
+115.29KB -
✅ guest-api.js
+40.23KB -

Total files change +15.72MB

Final result: ✅

View report in BundleMon website ➡️


Current branch size history

OBS_content_takeScreenshot now returns a Promise instead of blocking the caller.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

The context fallback can capture the wrong canvas, functional coverage is missing, and the backend dependency remains unshipped.

Review effort: Balanced
Findings: 1 Medium severity · 1 Low severity

Open (2)
What changed in this PR

Adds an OBS-backed hotkey for saving the program output as a PNG in the recording folder.

Changes:

  • Adds screenshot capture, notifications, and usage tracking.
  • Registers the service and hotkey with localized strings.
  • Updates hotkey settings coverage.
File Description
app/​services/​screenshot.ts Implements screenshot capture and notifications.
app/​services/​hotkeys.ts Adds the Screenshot Output hotkey.
app/​app-services.ts Registers the screenshot service.
app/​i18n/​en-US/​hotkeys.json Adds user-facing strings.
test/​regular/​settings/​hotkeys.ts Verifies the hotkey is listed.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread app/services/screenshot.ts Outdated
@Inject() private jsonrpcService: JsonrpcService;
@Inject() private usageStatisticsService: UsageStatisticsService;

async takeScreenshot(display: TDisplayType = 'horizontal') {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed; deferred until the osn release with OBS_content_takeScreenshot is pinned. Before that, a test can only reach the needs-newer-backend path.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Falling back to the horizontal canvas paired it with vertical recording settings; a missing context now takes the existing warning path.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@summeroff

Copy link
Copy Markdown
Contributor

require changes coming with this OSN version #6250

@au-sf
au-sf changed the base branch from master to staging October 2, 2026 16:16
@au-sf
au-sf marked this pull request as ready for review October 2, 2026 16:29
Copilot AI balanced review requested due to automatic review settings October 2, 2026 16:29

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

The screenshot test cannot pass until the required backend dependency is pinned.

Review effort: Balanced
Findings: 1 High severity · 1 Low severity

Open (2)
Resolved since last review (1)

Comment thread test/regular/screenshot.ts
@michelinewu michelinewu added ready for review needs QA requires QA testing on the branch before merging labels Oct 2, 2026

This branch has not been deployed

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

Labels

needs QA requires QA testing on the branch before merging ready for review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants