Skip to content

Commit 59e94c2

Browse files
synleclaude
andcommitted
fix: preserve client-side brightness on monitor refetch (v6.4.5)
Sliding a monitor's brightness occasionally snapped back because get_monitors / fetch_all_state reads brightness from the monitor over DDC, and DDC takes time to settle. A refetch triggered during that window (popup re-show, visibilitychange, monitors-changed event) would return a stale value and clobber the user's intended setting. Make the client the source of truth for brightness/contrast: when refetching, merge by uid and keep the existing brightness/contrast values. Monitor list, names, hidden flag, and contrast capability still come from the backend. Co-Authored-By: Claude Opus 4 (1M context) <noreply@anthropic.com>
1 parent 22872cd commit 59e94c2

2 files changed

Lines changed: 25 additions & 5 deletions

File tree

src-tauri/tauri.conf.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"$schema": "https://raw.githubusercontent.com/nicoverbruggen/tauri-v2-docs/refs/heads/main/schemas/config.schema.json",
33
"productName": "Display DJ",
4-
"version": "6.4.4",
4+
"version": "6.4.5",
55
"identifier": "com.synle.display-dj",
66
"build": {
77
"beforeDevCommand": "npm run dev",

src/App.tsx

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,33 @@ function App() {
3131
const [version, setVersion] = useState('');
3232
const appRef = useRef<HTMLDivElement>(null);
3333

34+
/** Merges refetched monitors with the current state, preserving client-side
35+
* brightness/contrast values. The client is the source of truth for these —
36+
* reading them back from the sidecar can return stale values while DDC
37+
* settles, which causes the slider to snap back to the pre-change value. */
38+
const mergeMonitors = useCallback((fetched: Monitor[], prev: Monitor[]): Monitor[] => {
39+
return fetched.map((m) => {
40+
const existing = prev.find((p) => p.uid === m.uid);
41+
if (!existing) return m;
42+
return {
43+
...m,
44+
brightness: existing.brightness,
45+
// Only preserve contrast when both sides still report DDC capability
46+
contrast:
47+
m.contrast !== null && existing.contrast !== null ? existing.contrast : m.contrast,
48+
};
49+
});
50+
}, []);
51+
3452
/** Fetches the list of connected monitors from the backend. */
3553
const fetchMonitors = useCallback(async () => {
3654
try {
3755
const m = await invoke<Monitor[]>('get_monitors');
38-
setMonitors(m);
56+
setMonitors((prev) => mergeMonitors(m, prev));
3957
} catch (e) {
4058
console.error('Failed to get monitors:', e);
4159
}
42-
}, []);
60+
}, [mergeMonitors]);
4361

4462
/** Fetches the current dark mode state from the backend. */
4563
const fetchDarkMode = useCallback(async () => {
@@ -91,13 +109,15 @@ function App() {
91109
isDark: boolean;
92110
volume: number;
93111
}>('fetch_all_state');
94-
setMonitors(state.monitors);
112+
setMonitors((prev) =>
113+
prev.length === 0 ? state.monitors : mergeMonitors(state.monitors, prev),
114+
);
95115
setDarkMode(state.isDark);
96116
setVolume(state.volume);
97117
} catch (e) {
98118
console.error('Failed to fetch all state:', e);
99119
}
100-
}, []);
120+
}, [mergeMonitors]);
101121

102122
useEffect(() => {
103123
fetchAllState();

0 commit comments

Comments
 (0)