feat(display.go): Add display brightness settings (#17)

* feat(display.go): impl setDisplayBrightness()

Implements setDisplayBrightness(brightness int) which allows setting the
backlight brightness on JetKVM's hardware.

Needs to be implemented into the RPC, config and frontend.

* feat(config): add backlight control settings

* feat(display): add automatic dimming & switch off to display

WIP, dims the display to 50% of the BacklightMaxBrightness after
BacklightDimAfterMS expires. Turns the display off after
BacklightOffAfterMS

* feat(rpc): add methods to get and set BacklightSettings

* WIP: feat(settings): add Max backlight setting

* chore: use constant for backlight control file

* fix: only attempt to wake the display if it's off

* feat(display): wake on touch

* fix: re-use buffer between reads

* fix: wakeDisplay() on start to fix warm start issue

If the application had turned off the display before exiting, it
wouldn't be brought on when the application restarted without a device
reboot.

* chore: various comment & string updates

* fix: newline on set brightness log

Noticed by @eric
https://github.com/jetkvm/kvm/pull/17#discussion_r1903338705

* fix: set default value for display

Set the DisplayMaxBrightness to the default brightness used
out-of-the-box by JetKVM. Also sets the dim/timeout to 2 minutes and 30
mintes respectively.

* feat(display.go): use tickers to countdown to dim/off

As suggested by tutman in https://github.com/jetkvm/kvm/pull/17, use
tickers set to the duration of dim/off to avoid a loop running every
second. The tickers are reset to the dim/off times whenever
wakeDisplay() is called.

* chore: update config

Changed Dim & Off values to seconds instead of milliseconds, there's no
need for it to be that precise.

* feat(display.go): wakeDisplay() force

Adds the force boolean to wakedisplay() which allows skipping the
backlightState == 0 check, this means you can force a ticker reset, even
if the display is currently in the "full bright" state

* feat(display.go): move tickers into their own method

This allows them to only be started if necessary. If the user has chosen
to keep the display on and not-dimmed all the time, the tickers can't
start as their tick value must be a positive integer.

* feat(display.go): stop tickers when auto-dim/auto-off is disabled

* feat(rpc): implement display backlight control methods

* feat(ui): implement display backlight control

* chore: update variable names

As part of @joshuasing's review on #17, updated variables & constants to
match the Go best practices.

Signed-off-by: Cameron Fleming <cameron@nevexo.space>

* fix(display): move backlightTicker setup into screen setup goroutine

Signed-off-by: Cameron Fleming <cameron@nevexo.space>

* chore: fix some start-up timing issues

* fix(display): Don't attempt to start the tickers if the display is disabled

If max_brightness is zero, then there's no point in trying to dim it (or
turn it off...)

* fix: wakeDisplay() doesn't need to stop the tickers

The tickers only need to be reset, if they're disabled, they won't have
been started.

* fix: Don't wake up the display if it's turned off

---------

Signed-off-by: Cameron Fleming <cameron@nevexo.space>
This commit is contained in:
Cameron Fleming
2025-02-17 10:00:28 +00:00
committed by GitHub
parent 951173ba19
commit 5217377175
5 changed files with 371 additions and 13 deletions

View File

@@ -1,5 +1,6 @@
import SidebarHeader from "@components/SidebarHeader";
import {
BacklightSettings,
useLocalAuthModalStore,
useSettingsStore,
useUiStore,
@@ -95,6 +96,7 @@ export default function SettingsSidebar() {
const hideCursor = useSettingsStore(state => state.isCursorHidden);
const setHideCursor = useSettingsStore(state => state.setCursorVisibility);
const setDeveloperMode = useSettingsStore(state => state.setDeveloperMode);
const setBacklightSettings = useSettingsStore(state => state.setBacklightSettings);
const [currentVersions, setCurrentVersions] = useState<{
appVersion: string;
@@ -228,6 +230,28 @@ export default function SettingsSidebar() {
[send, setDeveloperMode],
);
const handleBacklightSettingsChange = (settings: BacklightSettings) => {
// If the user has set the display to dim after it turns off, set the dim_after
// value to never.
if (settings.dim_after > settings.off_after && settings.off_after != 0) {
settings.dim_after = 0;
}
setBacklightSettings(settings);
}
const handleBacklightSettingsSave = () => {
send("setBacklightSettings", { params: settings.backlightSettings }, resp => {
if ("error" in resp) {
notifications.error(
`Failed to set backlight settings: ${resp.error.data || "Unknown error"}`,
);
return;
}
notifications.success("Backlight settings updated successfully");
});
};
const handleUpdateSSHKey = useCallback(() => {
send("setSSHKeyState", { sshKey }, resp => {
if ("error" in resp) {
@@ -302,6 +326,17 @@ export default function SettingsSidebar() {
}
});
send("getBacklightSettings", {}, resp => {
if ("error" in resp) {
notifications.error(
`Failed to get backlight settings: ${resp.error.data || "Unknown error"}`,
);
return;
}
const result = resp.result as BacklightSettings;
setBacklightSettings(result);
})
send("getDevModeState", {}, resp => {
if ("error" in resp) return;
const result = resp.result as { enabled: boolean };
@@ -797,6 +832,80 @@ export default function SettingsSidebar() {
/>
</SettingsItem>
<div className="h-[1px] w-full bg-slate-800/10 dark:bg-slate-300/20" />
<div className="pb-2 space-y-4">
<SectionHeader
title="Hardware"
description="Configure the JetKVM Hardware"
/>
</div>
<SettingsItem title="Display Brightness" description="Set the brightness of the display">
<SelectMenuBasic
size="SM"
label=""
value={settings.backlightSettings.max_brightness.toString()}
options={[
{ value: "0", label: "Off" },
{ value: "10", label: "Low" },
{ value: "35", label: "Medium" },
{ value: "64", label: "High" },
]}
onChange={e => {
settings.backlightSettings.max_brightness = parseInt(e.target.value)
handleBacklightSettingsChange(settings.backlightSettings);
}}
/>
</SettingsItem>
{settings.backlightSettings.max_brightness != 0 && (
<>
<SettingsItem title="Dim Display After" description="Set how long to wait before dimming the display">
<SelectMenuBasic
size="SM"
label=""
value={settings.backlightSettings.dim_after.toString()}
options={[
{ value: "0", label: "Never" },
{ value: "60", label: "1 Minute" },
{ value: "300", label: "5 Minutes" },
{ value: "600", label: "10 Minutes" },
{ value: "1800", label: "30 Minutes" },
{ value: "3600", label: "1 Hour" },
]}
onChange={e => {
settings.backlightSettings.dim_after = parseInt(e.target.value)
handleBacklightSettingsChange(settings.backlightSettings);
}}
/>
</SettingsItem>
<SettingsItem title="Turn off Display After" description="Set how long to wait before turning off the display">
<SelectMenuBasic
size="SM"
label=""
value={settings.backlightSettings.off_after.toString()}
options={[
{ value: "0", label: "Never" },
{ value: "300", label: "5 Minutes" },
{ value: "600", label: "10 Minutes" },
{ value: "1800", label: "30 Minutes" },
{ value: "3600", label: "1 Hour" },
]}
onChange={e => {
settings.backlightSettings.off_after = parseInt(e.target.value)
handleBacklightSettingsChange(settings.backlightSettings);
}}
/>
</SettingsItem>
</>
)}
<p className="text-xs text-slate-600 dark:text-slate-400">
The display will wake up when the connection state changes, or when touched.
</p>
<Button
size="SM"
theme="primary"
text="Save Display Settings"
onClick={handleBacklightSettingsSave}
/>
<div className="h-[1px] w-full bg-slate-800/10 dark:bg-slate-300/20" />
<div className="pb-2 space-y-4">
<SectionHeader
title="Advanced"

View File

@@ -229,6 +229,12 @@ export interface VideoState {
}) => void;
}
export interface BacklightSettings {
max_brightness: number;
dim_after: number;
off_after: number;
}
export const useVideoStore = create<VideoState>(set => ({
width: 0,
height: 0,
@@ -270,6 +276,9 @@ interface SettingsState {
// Add new developer mode state
developerMode: boolean;
setDeveloperMode: (enabled: boolean) => void;
backlightSettings: BacklightSettings;
setBacklightSettings: (settings: BacklightSettings) => void;
}
export const useSettingsStore = create(
@@ -287,6 +296,13 @@ export const useSettingsStore = create(
// Add developer mode with default value
developerMode: false,
setDeveloperMode: enabled => set({ developerMode: enabled }),
backlightSettings: {
max_brightness: 100,
dim_after: 10000,
off_after: 50000,
},
setBacklightSettings: (settings: BacklightSettings) => set({ backlightSettings: settings }),
}),
{
name: "settings",