Skip to content

Configuration and profiles

NuclearMeltdown edited this page Oct 19, 2026 · 9 revisions

Configuration and profiles

src/config.cpp, src/config.h, src/json.cpp

Where it lives

qBlank.json, beside the executable. Nothing is written to the registry — the one exception being the virtual camera's COM registration, which has to be machine-wide and is not qBlank's own setting. See Virtual camera.

Written a second after the last change, or at once when saved on purpose. The text is serialised on the render thread, where the settings live, and handed to configJobs_ for the disk (see Latency). Writes run in order, so the last one wins. When Windows ends the session, the program waits for the write before it lets go.

A settings file written under a former name is recognised by what is in it, not by what it is called: FindForeignSettings() reads every .json beside the executable and picks out the ones that are this program's. Found alone, such a file is simply used from then on; found alongside a current one, the program asks which to keep, in the language the older file was written with. The loser is renamed to .bak rather than deleted. See Updates.

The JSON parser and writer are hand-written (src/json.cpp, 450 lines) rather than pulled in as a dependency. The file is small, the schema is known, and every read goes through a defaulting accessor:

app.settingsTab = a["settingsTab"].AsInt(0);

A missing or malformed key yields the default instead of an error, so a config file from an older build always loads.

Profiles

struct Profile {
  std::string name = "Standard";
  long autoSelectStandard = 0;  // which detected standard means this profile
  CaptureSettings capture;   // device, input, format, video standard
  ImageSettings image;       // scaling, deinterlacing, composite filter, crop, colour
  AudioSettings audio;       // devices, buffer, offset, volume, recording sources
};

One per source. Selected with Ctrl+1 to Ctrl+9, which is deliberately not rebindable — see Shortcuts.

A profile is everything that depends on what is plugged in. Switching profiles restarts the capture graph, because the device or the format may have changed.

Save current as …

The usual way to make one, and the reason there is no plain "Duplicate" any more. Connecting a second console almost never means starting over: you change what is different and want to keep the result. An empty profile forces you to set up everything that was already right.

It copies the active profile — which is the current state, since settings take effect immediately and are written there — and opens the name prompt with the keyboard focus already in the field. SetKeyboardFocusHere() fires only on the popup's first frame, or it would grab the input again every frame.

Choosing a profile by itself

App::UpdateProfileForStandard(), and one long in the profile:

long autoSelectStandard = 0;   // 0 = never

A profile can say which video standard means me. Set it in the profiles tab under Choose automatically, and when the automatic search settles on that standard, qBlank switches to that profile — the PAL console and the NTSC one on the same input, each with its own geometry, deinterlacer and colour settings, without picking from a menu.

It is stored on the profile and not next to the standard on the source page, although it is the same list. There the standard answers what should the card be set to; here it answers when am I meant. That is a statement about the profile, and it belongs where the profiles are.

Compared by group, not by value. The list offers eight groups — PAL, PAL 60, PAL M, PAL N, NTSC, NTSC Japan, NTSC 4.43, SECAM — and the rule matches any standard in the same one. PAL B and PAL G differ in the sound carrier, which is no part of the picture, and a rule meaning "the PAL console" must not miss because the search settled on the other spelling of PAL. The value written to the config is still an ordinary standard, so the file format does not grow a second vocabulary; only the comparison is by group.

The trigger is the end of stage two, not the lock. Until the colour round has settled, all that is known is a line count, and 525 lines are five different standards in four different groups. colourCheckedStandard_ is the value that says both stages are done; a second member remembers what was last acted on, so a standing state becomes a one-time event.

Four things stop a switch, and each of them is a case where switching would be wrong rather than merely unwanted:

Not switched Why
While recording, or while the crop tool is open Both would be interrupted by a graph rebuild
Onto a profile that pins a video standard It would never search, so its rule could never fire — and a rule that quietly does nothing is worse than no rule
Onto a profile on another device or another crossbar input The detected standard says nothing about a source that is not the one being watched
When the active profile's own rule already matches The loop guard

That last one is the one that matters. Two profiles both claiming PAL, without it, would hand the detection back and forth forever. With it, a profile that already answers to the standard on screen keeps it, and nothing moves. A profile chosen by hand stays until the source really changes.

The combo is disabled outright while the profile pins a standard, with the reason written under it rather than left to be worked out.

Settings that do not apply are not applied

Hiding a control is not enough. Somebody who had the dot crawl filters and a native pixel grid set for a SNES, and then plugs in an HD console, would otherwise be looking at a picture they did not ask for with no control anywhere to change it — the control is gone precisely because it does not fit.

App::EffectiveImage() returns the settings as they apply to the current source, and it is that copy which is drawn:

Neutralised When
chromaSoft, temporalDenoise, dotNotch, nativeWidth, lineDouble the source is not analogue
nativeWidth the source is taller than 576 lines, whatever sourceLines says
a hand-picked deinterlacer (forced to deinterlaceAuto) the source has no fields

It neutralises the copy, never the profile. The stored values survive, because a profile describes a console and that console will come back.

What it deliberately does not do is remember and restore across a source change. The next analogue source may be a different console wanting different values, so restoring would be a guess. That is what profiles are for.

The same predicate, App::SourceIsAnalogue(), drives both what the settings show and what the renderer applies. Two answers to one question would mean something acting where nothing can be adjusted.

Measured, with nativeWidth set to 256 against a 1080p source: with the signal type forced to Analogue the grid applies and a row shows 55 column transitions; on Automatic, where 1080 lines cannot have come from an analogue decoder, the same row shows 240. The setting is dropped, not merely hidden.

What is not in a profile — AppSettings — is everything that depends on you: theme, accent colour, language, vsync, always-on-top, cursor hiding, sleep prevention, the toolbar, the overlay level, where the settings window sits, which tab it was on, and every recording, encoder and update setting.

Settings that change meaning

Two places in the configuration carry an index into a table rather than a value, and both are noted in the source at the table:

  • videoStandard — the index into kStandards in src/capture/dshow_util.cpp. Entries may be appended but never reordered. Two values are not indices: 0 means leave the decoder alone and -1 means find it, which is what the automatic search reads.
  • speed — the old software encoder speed, kept working alongside the newer preset setting so nobody's existing configuration changes meaning. See Encoder settings.

Sleep prevention

preventSleep, on by default. Playing a game through a capture card involves no keyboard or mouse input as far as Windows is concerned, so the screen saver would come on mid-session. App calls SetThreadExecutionState periodically while a capture is running.

Old shader cache files

Builds before 5.5 compiled their Direct3D 11 shaders at the first start and kept them as shader-<hash>.cso next to the executable. The build compiles them now, the executable carries them, and Updater::CleanUpPreviousBuild() deletes any shader-*.cso it finds at startup.

Clone this wiki locally