# DisplaySettings Extension Recipe Header: `Tactility/Include/Tactility/settings/DisplaySettings.h` Impl: `Tactility/Source/settings/DisplaySettings.cpp` ## Field Types Struct as of 2026-07-11: ```cpp struct DisplaySettings { Orientation orientation; uint8_t gammaCurve; uint8_t backlightDuty; bool backlightTimeoutEnabled; uint32_t backlightTimeoutMs; ScreensaverType screensaverType = BouncingBalls; bool disableScreensaverWhenCharging = false; // new }; enum ScreensaverType { None, BouncingBalls, Mystify, MatrixRain, StackChan, McpScreen, Count }; ``` ## Adding a New Field — 5 Places in CPP 1. `SETTINGS_KEY_*` constant: ```cpp constexpr auto* SETTINGS_KEY_DISABLE_SCREENSAVER_WHEN_CHARGING = "disableScreensaverWhenCharging"; ``` 2. `load()` — parse: ```cpp bool disable_when_charging = false; auto e = map.find(KEY_DISABLE); if (e != map.end()) disable_when_charging = (e->second=="1"||e->second=="true"||e->second=="True"); ... settings.disableScreensaverWhenCharging = disable_when_charging; ``` 3. `getDefault()` aggregate includes field. 4. `save()`: ```cpp map[KEY_DISABLE] = settings.disableScreensaverWhenCharging ? "1":"0"; ``` 5. For enums: add `toString/fromString` cases. Sentinel `Count` is bounds check. ## Persistence Details - File: `getUserDataPath()+"/settings/display.properties"` (Internal or SD depending on device.properties storage.userDataLocation) - `loadPropertiesFile` → `map`, tolerant parsing `atoi`/`strtoul` - `savePropertiesFile` writes `key=value` lines. Parent dir created via `findOrCreateParentDirectory(path,0755)` - `loadOrGetDefault()` tries load, else getDefault() - Settings thread-safety: save dispatched off UI via `getMainDispatcher().dispatch([settings]{ save(settings); findService()->reloadSettings(); })` ## Reload into DisplayIdleService `reloadSettings()` sets `atomic settingsReloadRequested`. Next `tick()` (50ms timer, Lower priority) checks `exchange(false)` and does `cachedDisplaySettings = loadOrGetDefault()` while holding LVGL lock. ## Display App UI Wiring - Members stored as `lv_obj_t*` raw pointers, initialized nullptr - `onShow()` loads settings `loadOrGetDefault()`, builds wrappers column flex - Each row pattern: create wrapper, label left aligned, control right aligned (switch/dropdown/slider) - Switch state: `lv_obj_has_state(sw, LV_STATE_CHECKED)`; set via `lv_obj_add_state(sw, LV_STATE_CHECKED)` before aligning - Dependent disabling: parent `timeoutSwitch` toggles `timeoutDropdown`, `screensaverDropdown`, `disableWhenChargingWrapper` via `LV_STATE_DISABLED` - Event callbacks static, retrieve app via `lv_event_get_user_data(event)` cast to DisplayApp* - `onHide()` only saves if `displaySettingsUpdated==true`, dispatches async