fix(settings): preserve quick resume timeout preference (#2101)

## Summary

### **What is the goal of this PR?**
This fixes an unintended settings side effect when cycling the `Sleep
Screen` option through `Quick Resume`.

Previously, selecting `Sleep Screen = Quick Resume` globally forced
`Quick Resume on Timeout = ON` and left it enabled even after the user
toggled `Sleep Screen` to another option within the same settings
session. Now the auto-enable behavior is scoped to the Settings screen
session:

- If `Quick Resume on Timeout` was already `ON` when entering Settings,
it stays `ON`.
- If it was `OFF`, selecting `Sleep Screen = Quick Resume` temporarily
turns it `ON`.
- If the user then switches away from `Quick Resume`, it turns back
`OFF`.

### **What changes are included?**

- Removes the global logic that permanently forced `Quick Resume on
Timeout` to `ON` whenever `Sleep Screen` was set to `Quick Resume`, even
if it was just due to toggling through the options.
- Adds Settings-screen session tracking so `Quick Resume on Timeout` is
only auto-enabled while the user has `Sleep Screen = Quick Resume`.
- Restores `Quick Resume on Timeout` back to `OFF` when the user
switches away, but only if it was `OFF` when they entered Settings.
- Preserves existing `ON` timeout preferences.
- Same behavior applies to the web settings

## Additional Context

- Tested this on device and via the settings UI
---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it helps set the right
context for reviewers.

Did you use AI tools to help write this code? _**< YES  >**_
This commit is contained in:
Julia
2026-05-21 21:06:51 -04:00
committed by GitHub
parent 2dd491b62e
commit f39ba7037f
7 changed files with 138 additions and 17 deletions
-6
View File
@@ -79,12 +79,6 @@ void CrossPointSettings::validateFrontButtonMapping(CrossPointSettings& settings
}
}
void CrossPointSettings::normalizeDependentSettings(CrossPointSettings& settings) {
if (settings.sleepScreen == SLEEP_SCREEN_MODE::QUICK_RESUME) {
settings.quickResumeSleepScreen = QUICK_RESUME_SLEEP_SCREEN::QUICK_RESUME_AFTER_TIMEOUT;
}
}
bool CrossPointSettings::saveToFile() const {
Storage.mkdir("/.crosspoint");
return JsonSettingsIO::saveSettings(*this, SETTINGS_FILE_JSON);
-1
View File
@@ -275,7 +275,6 @@ class CrossPointSettings {
bool loadFromFile();
static void validateFrontButtonMapping(CrossPointSettings& settings);
static void normalizeDependentSettings(CrossPointSettings& settings);
private:
bool loadFromBinaryFile();
-4
View File
@@ -223,10 +223,6 @@ bool JsonSettingsIO::loadSettings(CrossPointSettings& s, const char* json, bool*
}
}
const uint8_t quickResumeBeforeNormalize = s.quickResumeSleepScreen;
CrossPointSettings::normalizeDependentSettings(s);
if (s.quickResumeSleepScreen != quickResumeBeforeNormalize && needsResave) *needsResave = true;
// Front button remap — managed by RemapFrontButtons sub-activity, not in SettingsList.
using S = CrossPointSettings;
s.frontButtonBack =
+30 -1
View File
@@ -86,6 +86,10 @@ void SettingsActivity::onEnter() {
// Reset selection to first category
selectedCategoryIndex = 0;
selectedSettingIndex = 0;
preserveQuickResumeTimeoutOn =
SETTINGS.quickResumeSleepScreen == CrossPointSettings::QUICK_RESUME_SLEEP_SCREEN::QUICK_RESUME_AFTER_TIMEOUT;
quickResumeTimeoutAutoEnabled = false;
syncQuickResumeTimeoutForSleepScreen(/*sleepScreenChanged=*/true, /*quickResumeTimeoutChanged=*/false);
rebuildSettingsLists();
@@ -176,6 +180,8 @@ void SettingsActivity::toggleCurrentSetting() {
}
const auto& setting = (*currentSettings)[selectedSetting];
const bool sleepScreenChanged = setting.valuePtr == &CrossPointSettings::sleepScreen;
const bool quickResumeTimeoutChanged = setting.valuePtr == &CrossPointSettings::quickResumeSleepScreen;
if (setting.type == SettingType::TOGGLE && setting.valuePtr != nullptr) {
// Toggle the boolean value using the member pointer
@@ -253,10 +259,33 @@ void SettingsActivity::toggleCurrentSetting() {
return;
}
CrossPointSettings::normalizeDependentSettings(SETTINGS);
syncQuickResumeTimeoutForSleepScreen(sleepScreenChanged, quickResumeTimeoutChanged);
SETTINGS.saveToFile();
}
void SettingsActivity::syncQuickResumeTimeoutForSleepScreen(bool sleepScreenChanged, bool quickResumeTimeoutChanged) {
if (quickResumeTimeoutChanged) {
preserveQuickResumeTimeoutOn =
SETTINGS.quickResumeSleepScreen == CrossPointSettings::QUICK_RESUME_SLEEP_SCREEN::QUICK_RESUME_AFTER_TIMEOUT;
quickResumeTimeoutAutoEnabled = false;
}
if (SETTINGS.sleepScreen == CrossPointSettings::SLEEP_SCREEN_MODE::QUICK_RESUME) {
if (SETTINGS.quickResumeSleepScreen != CrossPointSettings::QUICK_RESUME_SLEEP_SCREEN::QUICK_RESUME_AFTER_TIMEOUT) {
SETTINGS.quickResumeSleepScreen = CrossPointSettings::QUICK_RESUME_SLEEP_SCREEN::QUICK_RESUME_AFTER_TIMEOUT;
quickResumeTimeoutAutoEnabled = !preserveQuickResumeTimeoutOn;
} else if (sleepScreenChanged && !preserveQuickResumeTimeoutOn) {
quickResumeTimeoutAutoEnabled = true;
}
return;
}
if (sleepScreenChanged && quickResumeTimeoutAutoEnabled && !preserveQuickResumeTimeoutOn) {
SETTINGS.quickResumeSleepScreen = CrossPointSettings::QUICK_RESUME_SLEEP_SCREEN::QUICK_RESUME_NEVER;
quickResumeTimeoutAutoEnabled = false;
}
}
void SettingsActivity::render(RenderLock&&) {
renderer.clearScreen();
@@ -156,12 +156,16 @@ class SettingsActivity final : public Activity {
std::vector<SettingInfo> systemSettings;
const std::vector<SettingInfo>* currentSettings = nullptr;
bool preserveQuickResumeTimeoutOn = false;
bool quickResumeTimeoutAutoEnabled = false;
static constexpr int categoryCount = 4;
static const StrId categoryNames[categoryCount];
void enterCategory(int categoryIndex);
void toggleCurrentSetting();
void rebuildSettingsLists();
void syncQuickResumeTimeoutForSleepScreen(bool sleepScreenChanged, bool quickResumeTimeoutChanged);
public:
explicit SettingsActivity(GfxRenderer& renderer, MappedInputManager& mappedInput)
-1
View File
@@ -1265,7 +1265,6 @@ void CrossPointWebServer::handlePostSettings() {
}
}
CrossPointSettings::normalizeDependentSettings(SETTINGS);
SETTINGS.saveToFile();
LOG_DBG("WEB", "Applied %d setting(s)", applied);
+104 -4
View File
@@ -312,6 +312,11 @@
<script>
let allSettings = [];
let originalValues = {};
let preserveQuickResumeTimeoutOn = false;
let quickResumeTimeoutAutoEnabled = false;
const SLEEP_SCREEN_MODE = {
QUICK_RESUME: 6
};
function escapeHtml(unsafe) {
return unsafe
@@ -336,12 +341,12 @@
if (setting.type === 'toggle') {
const checked = setting.value ? 'checked' : '';
return '<label class="toggle-switch">' +
'<input type="checkbox" id="' + id + '" ' + checked + ' onchange="markChanged()">' +
'<input type="checkbox" id="' + id + '" ' + checked + ' onchange="handleSettingChanged(\'' + setting.key + '\')">' +
'<span class="toggle-slider"></span></label>';
}
if (setting.type === 'enum') {
let html = '<select id="' + id + '" onchange="markChanged()">';
let html = '<select id="' + id + '" onchange="handleSettingChanged(\'' + setting.key + '\')">';
setting.options.forEach(function(opt, idx) {
const selected = idx === setting.value ? ' selected' : '';
html += '<option value="' + idx + '"' + selected + '>' + escapeHtml(opt) + '</option>';
@@ -353,14 +358,14 @@
if (setting.type === 'value') {
return '<input type="number" id="' + id + '" value="' + setting.value + '"' +
' min="' + setting.min + '" max="' + setting.max + '" step="' + setting.step + '"' +
' onchange="markChanged()">';
' onchange="handleSettingChanged(\'' + setting.key + '\')">';
}
if (setting.type === 'string') {
const inputType = setting.name.toLowerCase().includes('password') ? 'password' : 'text';
const val = setting.value || '';
return '<input type="' + inputType + '" id="' + id + '" value="' + escapeHtml(val) + '"' +
' oninput="markChanged()">';
' oninput="handleSettingChanged(\'' + setting.key + '\')">';
}
return '';
@@ -389,6 +394,96 @@
document.getElementById('saveBtn').disabled = false;
}
function findSetting(key) {
return allSettings.find(function(s) { return s.key === key; });
}
function getControl(key) {
return document.getElementById('setting-' + key);
}
function getControlValue(key) {
const setting = findSetting(key);
const el = getControl(key);
if (!setting || !el) return undefined;
if (setting.type === 'toggle') {
return el.checked ? 1 : 0;
}
if (setting.type === 'enum' || setting.type === 'value') {
return parseInt(el.value, 10);
}
return el.value;
}
function setControlValue(key, value) {
const setting = findSetting(key);
const el = getControl(key);
if (!setting || !el) return false;
if (setting.type === 'toggle') {
const checked = !!value;
if (el.checked === checked) return false;
el.checked = checked;
return true;
}
const nextValue = String(value);
if (el.value === nextValue) return false;
el.value = nextValue;
return true;
}
function isQuickResumeSleepScreenSelected() {
const sleepScreen = findSetting('sleepScreen');
const sleepScreenValue = getControlValue('sleepScreen');
if (!sleepScreen || sleepScreenValue === undefined || !Number.isFinite(sleepScreenValue)) return false;
let selectedValue = sleepScreenValue;
if (Array.isArray(sleepScreen.values)) {
selectedValue = Number(sleepScreen.values[sleepScreenValue]);
} else if (Array.isArray(sleepScreen.options)) {
const selectedOption = sleepScreen.options[sleepScreenValue];
if (selectedOption && typeof selectedOption === 'object' && 'value' in selectedOption) {
selectedValue = Number(selectedOption.value);
}
}
return selectedValue === SLEEP_SCREEN_MODE.QUICK_RESUME;
}
function syncQuickResumeTimeoutForSleepScreen(sleepScreenChanged, quickResumeTimeoutChanged) {
const timeoutValue = getControlValue('quickResumeSleepScreen');
if (timeoutValue === undefined) return false;
let changed = false;
if (quickResumeTimeoutChanged) {
preserveQuickResumeTimeoutOn = timeoutValue === 1;
quickResumeTimeoutAutoEnabled = false;
}
if (isQuickResumeSleepScreenSelected()) {
if (timeoutValue !== 1) {
changed = setControlValue('quickResumeSleepScreen', 1);
quickResumeTimeoutAutoEnabled = !preserveQuickResumeTimeoutOn;
} else if (sleepScreenChanged && !preserveQuickResumeTimeoutOn) {
quickResumeTimeoutAutoEnabled = true;
}
return changed;
}
if (sleepScreenChanged && quickResumeTimeoutAutoEnabled && !preserveQuickResumeTimeoutOn) {
changed = setControlValue('quickResumeSleepScreen', 0);
quickResumeTimeoutAutoEnabled = false;
}
return changed;
}
function handleSettingChanged(key) {
syncQuickResumeTimeoutForSleepScreen(key === 'sleepScreen', key === 'quickResumeSleepScreen');
markChanged();
}
async function loadSettings() {
try {
const response = await fetch('/api/settings');
@@ -427,6 +522,11 @@
container.innerHTML = html;
document.getElementById('save-container').style.display = '';
document.getElementById('saveBtn').disabled = true;
preserveQuickResumeTimeoutOn = getControlValue('quickResumeSleepScreen') === 1;
quickResumeTimeoutAutoEnabled = false;
if (syncQuickResumeTimeoutForSleepScreen(true, false)) {
markChanged();
}
} catch (e) {
console.error(e);
document.getElementById('settings-container').innerHTML =