mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-10-23 11:44:00 -04:00
fix(icc): keep the attached profile when a gamma control is re-created
`setupOutputControls` stored a fresh `outputState` for every output it touched, and `createControls` calls it whenever the controls are rebuilt, so recreating a control dropped the output's `iccPath`/`iccProfile`/`outputTemp` — configured state that does not belong to the control. The path is dormant today because `destroyControls` only runs when nothing is configured (`needsControls()` covers `ICCProfiles`/`OutputTemps`), but the snapshot published after `createControls` would then report whatever the rebuilt state holds instead of the profile the display is configured with. `controlStateFor` reuses the state of a known output and resets only what belongs to the new control: the ramp bookkeeping and the failure budget (rampSize, failed, retryCount, lastFailTime, lastTemp, lastGamma, lastContrast). A control that has not reported gamma_size yet has to receive its ramp again, while the profile and the per-output temperature survive. An output seen for the first time still starts from a clean state. Covered by TestManager_ControlStateReuseKeepsAttachedICC.
This commit is contained in:
@@ -267,16 +267,40 @@ func (m *Manager) setupOutputControls(outputs []*wlclient.Output, manager *wlr_g
|
||||
}
|
||||
outputID := output.ID()
|
||||
registryName, _ := m.outputRegNames.Load(outputID)
|
||||
outState := &outputState{
|
||||
outState := m.controlStateFor(outputID, registryName, output, control)
|
||||
m.setupControlHandlers(outState, control)
|
||||
m.outputs.Store(outputID, outState)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// controlStateFor returns the state a (re)created gamma control is attached to.
|
||||
// A re-created control must not drop what belongs to the output rather than to
|
||||
// the control: the attached ICC profile and the per-output temperature survive,
|
||||
// while the ramp bookkeeping and the failure budget start over so the new
|
||||
// control receives a fresh ramp once it reports gamma_size.
|
||||
func (m *Manager) controlStateFor(outputID, registryName uint32, output *wlclient.Output, control any) *outputState {
|
||||
existing, ok := m.outputs.Load(outputID)
|
||||
if !ok {
|
||||
return &outputState{
|
||||
id: outputID,
|
||||
registryName: registryName,
|
||||
output: output,
|
||||
gammaControl: control,
|
||||
}
|
||||
m.setupControlHandlers(outState, control)
|
||||
m.outputs.Store(outputID, outState)
|
||||
}
|
||||
return nil
|
||||
|
||||
existing.registryName = registryName
|
||||
existing.output = output
|
||||
existing.gammaControl = control
|
||||
existing.rampSize = 0
|
||||
existing.failed = false
|
||||
existing.retryCount = 0
|
||||
existing.lastFailTime = time.Time{}
|
||||
existing.lastTemp = 0
|
||||
existing.lastGamma = 0
|
||||
existing.lastContrast = 0
|
||||
return existing
|
||||
}
|
||||
|
||||
func (m *Manager) setupControlHandlers(state *outputState, control *wlr_gamma_control.ZwlrGammaControlV1) {
|
||||
|
||||
@@ -631,6 +631,44 @@ func TestEffectiveTempTarget(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// A re-created gamma control (night light toggled, outputs re-enumerated) must
|
||||
// not drop the configured ICC state of the output it belongs to.
|
||||
func TestManager_ControlStateReuseKeepsAttachedICC(t *testing.T) {
|
||||
m := &Manager{}
|
||||
existing := &outputState{
|
||||
id: 1,
|
||||
registryName: 10,
|
||||
iccPath: "/tmp/display.icm",
|
||||
iccProfile: &icc.Profile{Description: "Test Display"},
|
||||
outputTemp: 7000,
|
||||
rampSize: 256,
|
||||
failed: true,
|
||||
retryCount: 3,
|
||||
lastFailTime: time.Now(),
|
||||
lastTemp: 7000,
|
||||
lastGamma: 1.1,
|
||||
lastContrast: 0.9,
|
||||
}
|
||||
m.outputs.Store(1, existing)
|
||||
|
||||
got := m.controlStateFor(1, 10, nil, "control-2")
|
||||
|
||||
assert.Same(t, existing, got, "the output keeps its state across control re-creation")
|
||||
assert.Equal(t, "/tmp/display.icm", got.iccPath, "the attached profile survives")
|
||||
assert.Equal(t, 7000, got.outputTemp, "the per-output temperature survives")
|
||||
assert.Equal(t, "control-2", got.gammaControl, "the new control is attached")
|
||||
|
||||
assert.Zero(t, got.rampSize, "the new control has not reported gamma_size yet")
|
||||
assert.False(t, got.failed)
|
||||
assert.Zero(t, got.retryCount)
|
||||
assert.Zero(t, got.lastTemp, "the ramp has to be written again")
|
||||
|
||||
fresh := m.controlStateFor(2, 20, nil, "control-3")
|
||||
assert.NotSame(t, existing, fresh)
|
||||
assert.Empty(t, fresh.iccPath, "an output seen for the first time starts clean")
|
||||
assert.Zero(t, fresh.outputTemp)
|
||||
}
|
||||
|
||||
// The status payload is what the settings UI shows for a profile, so the
|
||||
// descriptive metadata has to be carried through.
|
||||
func TestManager_GetICCStatusDescribesProfile(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user