Files
tactility/.claude/skills/tactility-firmware/references/display-settings.md
T
Adolfo Reyna 12035f2f39 chore: move firmware skills in-repo + AGENTS.md
- Migrates tactility-firmware and tactility-bluetooth from
  ~/.hermes/skills/ into .claude/skills/ for repo co-location
- Adds AGENTS.md with local workstation context, board table,
  board-direct build rule, Hermes PYTHONPATH pitfall, common
  Tactility pitfalls, and in-repo skill index

Refs: personal Gitea mirror
2026-07-17 09:51:08 -04:00

2.7 KiB

DisplaySettings Extension Recipe

Header: Tactility/Include/Tactility/settings/DisplaySettings.h Impl: Tactility/Source/settings/DisplaySettings.cpp

Field Types

Struct as of 2026-07-11:

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:
constexpr auto* SETTINGS_KEY_DISABLE_SCREENSAVER_WHEN_CHARGING = "disableScreensaverWhenCharging";
  1. load() — parse:
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;
  1. getDefault() aggregate includes field.

  2. save():

map[KEY_DISABLE] = settings.disableScreensaverWhenCharging ? "1":"0";
  1. 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)
  • loadPropertiesFilemap<string,string>, 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<bool> 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