qt: phase 8.2 — [[nodiscard]] on config:: accessors

Mark every config:: read function as [[nodiscard]]. The compiler now
catches the silent-drop bug class — calling config::get(&out, "key")
without checking the success bit leaves `out` untouched on failure,
which means whatever the caller put there earlier is what gets used.
Fine when the variable is intentionally pre-initialized to a fallback,
crash-prone otherwise.

Six call sites genuinely use the default-on-failure pattern; each is
made explicit with `(void)config::get(...)` plus a one-liner comment
stating that the success bit isn't load-bearing:

- quickterm::animationMs (`quick-terminal-animation-duration`)
- quickterm::setupLayerShell (`quick-terminal-size`)
- MainWindow::newWindow (`initial-window`)
- MainWindow::refreshChrome (`quit-after-last-window-closed`)
- MainWindow::applyBlur (`background-blur`)
- GhosttySurface::paintEvent (`unfocused-split-opacity`)

Every other config:: callsite already inspects the return.

Co-Authored-By: claude-flow <ruv@ruv.net>
pull/12846/head
ntomsic 2026-05-23 16:01:36 -05:00
parent 77deb24fee
commit a3ad905811
4 changed files with 29 additions and 18 deletions

View File

@ -317,8 +317,10 @@ void GhosttySurface::paintEvent(QPaintEvent *) {
// Unfocused-split dimming: a translucent fill over an inactive pane.
// Only split panes (a QSplitter parent) are dimmed, matching GTK.
if (!hasFocus() && qobject_cast<QSplitter *>(parentWidget())) {
double opacity = 0.7;
config::get(&opacity, "unfocused-split-opacity");
double opacity = 0.7; // default: 70% opaque
// On read failure opacity keeps the default; the success bit
// isn't load-bearing.
(void)config::get(&opacity, "unfocused-split-opacity");
if (opacity < 1.0) {
QColor fill(0, 0, 0); // default: dim toward black
ghostty_config_color_s c{};

View File

@ -289,7 +289,8 @@ MainWindow *MainWindow::newWindow(ghostty_surface_t parent) {
if (!s_initialWindowConsumed) {
s_initialWindowConsumed = true;
bool initialWindow = true;
config::get(&initialWindow, "initial-window");
// Default-on; the success bit isn't load-bearing.
(void)config::get(&initialWindow, "initial-window");
wantsShow = initialWindow;
}
if (wantsShow) w->show();
@ -1068,7 +1069,8 @@ void MainWindow::refreshChrome() {
// runtime; mirrors the calculation in initialize().
if (GhosttyApp::instance().config()) {
bool quitAfter = true;
config::get(&quitAfter, "quit-after-last-window-closed");
// Default-on; the success bit isn't load-bearing.
(void)config::get(&quitAfter, "quit-after-last-window-closed");
// Same Duration-decode workaround as initialize().
const uint64_t delayNs =
config::durationNs("quit-after-last-window-closed-delay", 0);
@ -1370,9 +1372,10 @@ void MainWindow::toggleCommandPalette(GhosttySurface *surface) {
void MainWindow::applyBlur() {
// background-blur is a union whose C value is an i16: 0 (and the
// macOS-only negatives) means off, a positive radius means on. KWin
// uses its own configured radius, so only on/off matters here.
// uses its own configured radius, so only on/off matters here. On
// read failure blur stays 0 (off).
short blur = 0;
config::get(&blur, "background-blur");
(void)config::get(&blur, "background-blur");
applyWindowBlur(this, blur > 0);
}

View File

@ -20,17 +20,17 @@ namespace config {
// The live ghostty_config_t. Returns nullptr before the singleton has
// finished ensureInitialized — callers that read config during early
// startup (before the first MainWindow::initialize) must check.
ghostty_config_t handle();
[[nodiscard]] ghostty_config_t handle();
// Read a string-valued config key (or an enum, which libghostty
// returns as its tag-name string). Empty if absent or the call
// fails.
QString string(const char *key);
[[nodiscard]] QString string(const char *key);
// Read a bool-valued config key. Returns `fallback` when the key is
// absent or the call fails. Note: libghostty's bool config keys are
// strict bools, NOT packed bitfields — see bitfield<>() for those.
bool boolean(const char *key, bool fallback);
[[nodiscard]] bool boolean(const char *key, bool fallback);
// Parse a duration config key as nanoseconds via the on-disk
// fallback. Use this for `?Duration` (optional) keys: c_get.zig
@ -41,18 +41,18 @@ bool boolean(const char *key, bool fallback);
// with `unsigned long long` and a manual ms→ns multiplication, NOT
// this wrapper, to avoid a redundant disk re-scan on every read.
// Returns `fallbackNs` on parse failure or absent key.
uint64_t durationNs(const char *key, uint64_t fallbackNs);
[[nodiscard]] uint64_t durationNs(const char *key, uint64_t fallbackNs);
// Scan the user's primary on-disk config file for `key = value`
// directly. Used for keys ghostty_config_get can't decode (Duration,
// repeating paths). Returns the last matching value, or empty.
QString diskValue(const char *key);
[[nodiscard]] QString diskValue(const char *key);
// True if the live config has any custom-shader entry. Drives
// GhosttySurface's premultiply pass — `custom-shader` is a
// repeatable path that ghostty_config_get can't expose, so we scan
// the on-disk config text directly.
bool hasCustomShader();
[[nodiscard]] bool hasCustomShader();
// Read a packed-bitfield config key. libghostty serializes packed
// structs as a c_uint via c_get.zig (`ptr.* = @intCast(@as(Backing,
@ -61,14 +61,14 @@ bool hasCustomShader();
// for a one-field packed struct) under-sizes the write and corrupts
// adjacent stack — always go through this. Returns `fallbackBits`
// when ghostty_config_get fails.
unsigned int bitfield(const char *key, unsigned int fallbackBits);
[[nodiscard]] unsigned int bitfield(const char *key, unsigned int fallbackBits);
// Read a path-valued disk config and expand a leading `~/` to the
// user's home directory. Returns empty when the key is absent.
// Path-valued keys are read off-disk (libghostty doesn't surface
// them through ghostty_config_get) so this is just diskValue() with
// a tilde-expansion pass.
QString expandedPath(const char *key);
[[nodiscard]] QString expandedPath(const char *key);
// Wrapper around ghostty_config_get that infers the value's length
// from a string literal so call sites stop repeating qstrlen(). The
@ -79,9 +79,11 @@ QString expandedPath(const char *key);
// `out` must point to the type ghostty.h documents for the key
// (bool* for bool keys, ghostty_config_color_s* for colors, etc.).
// Returns false when the key is absent or the underlying call
// fails.
// fails. The success bit MUST be checked — `out` is left untouched
// on failure, so dropping the return masks an unread / unwritten
// access.
template <typename T, size_t N>
inline bool get(T *out, const char (&key)[N]) {
[[nodiscard]] inline bool get(T *out, const char (&key)[N]) {
static_assert(N > 1, "config::get requires a non-empty key literal");
ghostty_config_t cfg = handle();
return cfg && ghostty_config_get(cfg, out, key, N - 1);

View File

@ -36,7 +36,9 @@ constexpr const char *kAnimProperty = "ghastty.quickterm.anim";
// and a very large value doesn't lock the user out.
int animationMs() {
double secs = 0.2; // matches Config.zig default
config::get(&secs, "quick-terminal-animation-duration");
// On read failure secs keeps the default; the success bit isn't
// load-bearing.
(void)config::get(&secs, "quick-terminal-animation-duration");
if (secs <= 0.0) return 0;
return std::clamp(static_cast<int>(secs * 1000.0), 1, 1000);
}
@ -117,8 +119,10 @@ void setupLayerShell(QWidget *window) {
const QSize scr = screen ? screen->size() : QSize(1920, 1080);
// quick-terminal-size: primary is the edge-perpendicular extent.
// On read failure qsz stays zero-initialized and toPx falls back to
// its `fallback` argument; the success bit isn't load-bearing.
ghostty_config_quick_terminal_size_s qsz = {};
config::get(&qsz, "quick-terminal-size");
(void)config::get(&qsz, "quick-terminal-size");
const auto toPx = [](const ghostty_quick_terminal_size_s &s, int dim,
int fallback) -> int {
switch (s.tag) {