diff --git a/surfaces/quickshell/modules/settings/IdleConfig.qml b/surfaces/quickshell/modules/settings/IdleConfig.qml index 19259a3..de36cc6 100644 --- a/surfaces/quickshell/modules/settings/IdleConfig.qml +++ b/surfaces/quickshell/modules/settings/IdleConfig.qml @@ -58,23 +58,38 @@ ContentPage { { displayName: Translation.tr("5 min"), icon: "timer", value: 300 }, { displayName: Translation.tr("10 min"), icon: "timer", value: 600 } ] - // How long before the lock the dim starts. Wider range than the - // lock-screen grace below: this one has a whole idle-to-lock budget to sit - // inside, and 300 with a 10-minute lock is Casey's "the 15s dim starts at - // 9:45" shape at a larger scale. - readonly property var dimGracePresets: [ - { displayName: Translation.tr("15s"), icon: "brightness_medium", value: 15 }, + // ONE dim setting, for both authorities. + // + // This used to be two: a session dim (15s–5min, before the lock) and a + // separate lock-screen dim grace (5–20s, before the blank). They are two + // daemons, but they are not two questions — the user is answering "how + // much warning do I get before the screen goes away", once. Splitting it + // made the page describe our architecture instead of their screen. + // + // Written to both: the shell's dimBeforeLockSeconds and sessiond's + // dim_grace_secs. The lock-screen side is clamped below, because a dim + // longer than the blank budget is a dim that never shows. + readonly property var dimPresets: [ + { displayName: Translation.tr("5s"), icon: "brightness_medium", value: 5 }, + { displayName: Translation.tr("10s"), icon: "brightness_medium", value: 10 }, { displayName: Translation.tr("30s"), icon: "brightness_medium", value: 30 }, { displayName: Translation.tr("1 min"), icon: "brightness_medium", value: 60 }, - { displayName: Translation.tr("3 min"), icon: "brightness_medium", value: 180 }, - { displayName: Translation.tr("5 min"), icon: "brightness_medium", value: 300 } - ] - readonly property var gracePresets: [ - { displayName: Translation.tr("5s"), icon: "brightness_medium", value: 5 }, - { displayName: Translation.tr("10s"), icon: "brightness_medium", value: 10 }, - { displayName: Translation.tr("20s"), icon: "brightness_medium", value: 20 } + { displayName: Translation.tr("3 min"), icon: "brightness_medium", value: 180 } ] + // The lock screen cannot warn for longer than it waits. Clamp rather than + // refuse: the user picked a warning length, and the honest reading of + // "30s warning, 15s blank" is "warn as early as this screen allows". + function applyDim(v) { + Config.options.lock.idle.dimBeforeLockSeconds = v; + if (!SessiondPolicy.available) + return; + const blank = SessiondPolicy.lockBlankAfterSecs; + SessiondPolicy.apply({ + dim_grace_secs: blank > 0 ? Math.max(1, Math.min(v, blank - 1)) : v + }); + } + // "2 min" reads better than "120 s" in a sentence about when things happen. function clockText(secs) { if (secs >= 60 && secs % 60 === 0) @@ -171,22 +186,48 @@ ContentPage { options: page.idlePresets } - // Relative, not absolute. The two used to be independent seconds - // values and nothing stopped a dim set past the lock, which produced - // a dim that silently never fired. Choosing "how long before" makes - // the relationship the thing the user sets, so moving the lock moves - // the dim with it. + StyledText { + Layout.fillWidth: true + visible: !Config.options.lock.idle.nativeCoordinatorEnabled + wrapMode: Text.WordWrap + color: Appearance.colors.colError + font.pixelSize: Appearance.font.pixelSize.smaller + text: Translation.tr("The native coordinator is off, so these do not run.") + } + } + + // --- Brightness dimming, one question --------------------------------- + // Deliberately not split by authority. Two daemons own the actuation, and + // the page used to say so by giving each its own dim control — which made + // the user answer the same question twice and left them to work out that + // the two interact. One control, written to both. See applyDim(). + ContentSection { + icon: "brightness_medium" + title: Translation.tr("Brightness dimming") + + ConfigSwitch { + buttonIcon: "brightness_low" + text: Translation.tr("Dim before the screen goes away") + checked: SessiondPolicy.dimWarning + enabled: SessiondPolicy.available + onCheckedChanged: { + if (SessiondPolicy.available && checked !== SessiondPolicy.dimWarning) + SessiondPolicy.apply({ dim_warning: checked }); + } + } + ContentSubsectionLabel { - text: Translation.tr("Start dimming this long before locking") + text: Translation.tr("Start dimming this long before") } ConfigSelectionArray { currentValue: Config.options.lock.idle.dimBeforeLockSeconds - onSelected: v => Config.options.lock.idle.dimBeforeLockSeconds = v - options: page.dimGracePresets + onSelected: v => page.applyDim(v) + options: page.dimPresets } - // Say what the pair actually does. A relationship the user has to - // compute in their head is one they will get wrong. + // Say what it actually does, on both surfaces, in one sentence each. + // A relationship the user has to compute in their head is one they + // will get wrong. StyledText { Layout.fillWidth: true wrapMode: Text.WordWrap @@ -196,7 +237,7 @@ ContentPage { const lock = Config.options.lock.idle.lockAfterSeconds; const grace = Config.options.lock.idle.dimBeforeLockSeconds; const dimAt = Math.max(1, Math.min(lock - 1, lock - grace)); - return Translation.tr("Dims at %1, locks at %2.") + return Translation.tr("In use: dims at %1, locks at %2.") .arg(page.clockText(dimAt)) .arg(page.clockText(lock)); } @@ -204,11 +245,19 @@ ContentPage { StyledText { Layout.fillWidth: true - visible: !Config.options.lock.idle.nativeCoordinatorEnabled + visible: SessiondPolicy.available wrapMode: Text.WordWrap - color: Appearance.colors.colError + color: Appearance.colors.colSubtext font.pixelSize: Appearance.font.pixelSize.smaller - text: Translation.tr("The native coordinator is off, so these do not run.") + text: { + const blank = SessiondPolicy.lockBlankAfterSecs; + if (blank === 0) + return Translation.tr("On the lock screen: never blanks, so nothing dims."); + const g = SessiondPolicy.dimGraceSecs; + return Translation.tr("On the lock screen: dims %1 before blanking at %2.") + .arg(page.clockText(g)) + .arg(page.clockText(blank)); + } } } @@ -245,51 +294,32 @@ ContentPage { // desk-clock case" — and nobody reasons about a lock screen in // 5-second increments. Values are still seconds on the wire; the // daemon's vocabulary does not change because the UI got legible. + // One blank timeout, not two. + // + // The daemon still has a separate held-vs-resting budget, and it is + // still the right idea — a phone in your hand should not blank on the + // same schedule as one face-up on a desk. It is not a *setting* + // though. Asking the user to pick two numbers made them responsible + // for arbitrating a guess the accelerometer was making on their + // behalf, and the machine already has a better vocabulary for that: + // §4's confidence arithmetic. Held-ness belongs there, as an + // adjustment to one budget, not as a second budget on this page. + // + // Until that lands, both fields get the same value, so the behaviour + // is uniform and predictable rather than silently forking on a sensor + // reading nothing surfaces. ContentSubsectionLabel { - text: Translation.tr("Blank after — on a table") + text: Translation.tr("Blank after") } ConfigSelectionArray { currentValue: SessiondPolicy.lockBlankAfterSecs - onSelected: v => SessiondPolicy.apply({ lock_blank_after_secs: v }) + onSelected: v => SessiondPolicy.apply({ + lock_blank_after_secs: v, + lock_blank_after_held_secs: v + }) options: page.blankPresets } - ContentSubsectionLabel { - text: Translation.tr("Blank after — in your hand") - } - StyledText { - Layout.fillWidth: true - wrapMode: Text.WordWrap - color: Appearance.colors.colSubtext - font.pixelSize: Appearance.font.pixelSize.smaller - text: Translation.tr("Used when the accelerometer says the device is being held rather than resting.") - } - ConfigSelectionArray { - currentValue: SessiondPolicy.lockBlankAfterHeldSecs - onSelected: v => SessiondPolicy.apply({ lock_blank_after_held_secs: v }) - options: page.blankPresets - } - - ContentSubsectionLabel { - text: Translation.tr("Dim warning before blanking") - } - ConfigSelectionArray { - currentValue: SessiondPolicy.dimGraceSecs - onSelected: v => SessiondPolicy.apply({ dim_grace_secs: v }) - options: page.gracePresets - } - - ConfigSwitch { - buttonIcon: "brightness_low" - text: Translation.tr("Show the dim warning") - checked: SessiondPolicy.dimWarning - enabled: SessiondPolicy.available - onCheckedChanged: { - if (SessiondPolicy.available && checked !== SessiondPolicy.dimWarning) - SessiondPolicy.apply({ dim_warning: checked }); - } - } - // Only claim the ordering guarantee when the daemon actually reports // the field. An older sessiond has no lock_ack_budget_secs, and // rendering that absence as "waits 0s" would be the page inventing a