Compare commits

...

6 Commits

Author SHA1 Message Date
Cameron Fleming a6eab94e0d feat(ui): implement display backlight control 2025-01-27 20:56:52 +00:00
Cameron Fleming 309d30d3c3 feat(rpc): implement display backlight control methods 2025-01-27 20:56:52 +00:00
Cameron Fleming cabe5b07ab feat(display.go): stop tickers when auto-dim/auto-off is disabled 2025-01-27 20:51:12 +00:00
Cameron Fleming 7d1777985f 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.
2025-01-27 20:51:12 +00:00
Cameron Fleming e9f140c735 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
2025-01-27 20:51:12 +00:00
Cameron Fleming 34e42fd37c chore: update config
Changed Dim & Off values to seconds instead of milliseconds, there's no
need for it to be that precise.
2025-01-27 20:51:12 +00:00
4 changed files with 148 additions and 54 deletions

View File

@ -23,8 +23,8 @@ type Config struct {
LocalAuthMode string `json:"localAuthMode"` //TODO: fix it with migration
WakeOnLanDevices []WakeOnLanDevice `json:"wake_on_lan_devices"`
DisplayMaxBrightness int `json:"display_max_brightness"`
DisplayDimAfterMs int64 `json:"display_dim_after_ms"`
DisplayOffAfterMs int64 `json:"display_off_after_ms"`
DisplayDimAfterSec int `json:"display_dim_after_sec"`
DisplayOffAfterSec int `json:"display_off_after_sec"`
}
const configPath = "/userdata/kvm_config.json"
@ -33,8 +33,8 @@ var defaultConfig = &Config{
CloudURL: "https://api.jetkvm.com",
AutoUpdateEnabled: true, // Set a default value
DisplayMaxBrightness: 64,
DisplayDimAfterMs: 120000, // 2 minutes
DisplayOffAfterMs: 1800000, // 30 minutes
DisplayDimAfterSec: 120, // 2 minutes
DisplayOffAfterSec: 1800, // 30 minutes
}
var config *Config

View File

@ -77,7 +77,7 @@ func requestDisplayUpdate() {
return
}
go func() {
wakeDisplay()
wakeDisplay(false)
fmt.Println("display updating........................")
//TODO: only run once regardless how many pending updates
updateDisplay()
@ -149,22 +149,28 @@ func tick_displayOff() {
// wakeDisplay sets the display brightness back to config.DisplayMaxBrightness and stores the time the display
// last woke, ready for displayTimeoutTick to put the display back in the dim/off states.
func wakeDisplay() {
if backlightState == 0 {
// Set force to true to skip the backlight state check, this should be done if altering the tickers.
func wakeDisplay(force bool) {
if backlightState == 0 && !force {
return
}
if config.DisplayMaxBrightness == 0 {
config.DisplayMaxBrightness = 100
}
err := setDisplayBrightness(config.DisplayMaxBrightness)
if err != nil {
fmt.Printf("display wake failed, %s\n", err)
}
dim_ticker.Reset(time.Duration(config.DisplayDimAfterMs) * time.Millisecond)
off_ticker.Reset(time.Duration(config.DisplayOffAfterMs) * time.Millisecond)
if config.DisplayDimAfterSec == 0 {
dim_ticker.Stop()
} else {
dim_ticker.Reset(time.Duration(config.DisplayDimAfterSec) * time.Second)
}
if config.DisplayOffAfterSec == 0 {
off_ticker.Stop()
} else {
off_ticker.Reset(time.Duration(config.DisplayOffAfterSec) * time.Second)
}
backlightState = 0
}
@ -192,7 +198,43 @@ func watchTsEvents() {
return
}
wakeDisplay()
wakeDisplay(false)
}
}
// startBacklightTickers starts the two tickers for dimming and switching off the display
// if they're not already set. This is done separately to the init routine as the "never dim"
// option has the value set to zero, but time.NewTicker only accept positive values.
func startBacklightTickers() {
LoadConfig()
if dim_ticker == nil && config.DisplayDimAfterSec != 0 {
fmt.Printf("display: dim_ticker has started.")
dim_ticker = time.NewTicker(time.Duration(config.DisplayDimAfterSec) * time.Second)
defer dim_ticker.Stop()
go func() {
for {
select {
case <-dim_ticker.C:
tick_displayDim()
}
}
}()
}
if off_ticker == nil && config.DisplayOffAfterSec != 0 {
fmt.Printf("display: off_ticker has started.")
off_ticker = time.NewTicker(time.Duration(config.DisplayOffAfterSec) * time.Second)
defer off_ticker.Stop()
go func() {
for {
select {
case <-off_ticker.C:
tick_displayOff()
}
}
}()
}
}
@ -204,28 +246,11 @@ func init() {
updateStaticContents()
displayInited = true
fmt.Println("display inited")
wakeDisplay()
wakeDisplay(false)
requestDisplayUpdate()
}()
go func() {
LoadConfig()
// Start display auto-sleeping tickers
dim_ticker = time.NewTicker(time.Duration(config.DisplayDimAfterMs) * time.Millisecond)
defer dim_ticker.Stop()
off_ticker = time.NewTicker(time.Duration(config.DisplayOffAfterMs) * time.Millisecond)
defer off_ticker.Stop()
for {
select {
case <-dim_ticker.C:
tick_displayDim()
case <-off_ticker.C:
tick_displayOff()
}
}
}()
startBacklightTickers()
go watchTsEvents()
}

View File

@ -225,13 +225,14 @@ func rpcTryUpdate() error {
return nil
}
func rpcSetBacklightSettings(data *BacklightSettings) error {
func rpcSetBacklightSettings(params BacklightSettings) error {
LoadConfig()
blConfig := *data
blConfig := params
if blConfig.MaxBrightness > 100 || blConfig.MaxBrightness < 0 {
return fmt.Errorf("maxBrightness must be between 0 and 100")
// NOTE: by default, the frontend limits the brightness to 64, as that's what the device originally shipped with.
if blConfig.MaxBrightness > 255 || blConfig.MaxBrightness < 0 {
return fmt.Errorf("maxBrightness must be between 0 and 255")
}
if blConfig.DimAfter < 0 {
@ -243,12 +244,24 @@ func rpcSetBacklightSettings(data *BacklightSettings) error {
}
config.DisplayMaxBrightness = blConfig.MaxBrightness
config.DisplayDimAfterMs = int64(blConfig.DimAfter)
config.DisplayOffAfterMs = int64(blConfig.OffAfter)
config.DisplayDimAfterSec = blConfig.DimAfter
config.DisplayOffAfterSec = blConfig.OffAfter
if err := SaveConfig(); err != nil {
return fmt.Errorf("failed to save config: %w", err)
}
log.Printf("rpc: display: settings applied, max_brightness: %d, dim after: %ds, off after: %ds", config.DisplayMaxBrightness, config.DisplayDimAfterSec, config.DisplayOffAfterSec)
// If the device started up with auto-dim and/or auto-off set to zero, the display init
// method will not have started the tickers. So in case that has changed, attempt to start the tickers now.
startBacklightTickers()
// Wake the display after the settings are altered, this ensures the tickers
// are reset to the new settings, and will bring the display up to maxBrightness.
// Calling with force set to true, to ignore the current state of the display, and force
// it to reset the tickers.
wakeDisplay(true)
return nil
}
@ -257,8 +270,8 @@ func rpcGetBacklightSettings() (*BacklightSettings, error) {
return &BacklightSettings{
MaxBrightness: config.DisplayMaxBrightness,
DimAfter: int(config.DisplayDimAfterMs),
OffAfter: int(config.DisplayOffAfterMs),
DimAfter: int(config.DisplayDimAfterSec),
OffAfter: int(config.DisplayOffAfterSec),
}, nil
}
@ -422,7 +435,7 @@ func callRPCHandler(handler RPCHandler, params map[string]interface{}) (interfac
}
args[i] = reflect.ValueOf(newStruct).Elem()
} else {
return nil, fmt.Errorf("invalid parameter type for: %s", paramName)
return nil, fmt.Errorf("invalid parameter type for: %s, type: %s", paramName, paramType.Kind())
}
} else {
args[i] = convertedValue.Convert(paramType)
@ -597,6 +610,6 @@ var rpcHandlers = map[string]RPCHandler{
"getWakeOnLanDevices": {Func: rpcGetWakeOnLanDevices},
"setWakeOnLanDevices": {Func: rpcSetWakeOnLanDevices, Params: []string{"params"}},
"resetConfig": {Func: rpcResetConfig},
"setBacklightSettings": {Func: rpcSetBacklightSettings, Params: []string{"settings"}},
"setBacklightSettings": {Func: rpcSetBacklightSettings, Params: []string{"params"}},
"getBacklightSettings": {Func: rpcGetBacklightSettings},
}

View File

@ -230,8 +230,18 @@ export default function SettingsSidebar() {
[send, setDeveloperMode],
);
const handleBacklightSettingChange = useCallback((settings: BacklightSettings) => {
send("setBacklightSettings", { settings }, resp => {
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"}`,
@ -240,7 +250,7 @@ export default function SettingsSidebar() {
}
notifications.success("Backlight settings updated successfully");
});
}, [send]);
};
const handleUpdateSSHKey = useCallback(() => {
send("setSSHKeyState", { sshKey }, resp => {
@ -829,7 +839,6 @@ export default function SettingsSidebar() {
/>
</div>
<SettingsItem title="Display Brightness" description="Set the brightness of the display">
{/* TODO: Allow the user to pick any value between 0 and 100 */}
<SelectMenuBasic
size="SM"
label=""
@ -837,18 +846,65 @@ export default function SettingsSidebar() {
options={[
{ value: "0", label: "Off" },
{ value: "10", label: "Low" },
{ value: "50", label: "Medium" },
{ value: "100", label: "High" },
{ value: "35", label: "Medium" },
{ value: "64", label: "High" },
]}
onChange={e => {
handleBacklightSettingChange({
max_brightness: parseInt(e.target.value),
dim_after: settings.backlightSettings.dim_after,
off_after: settings.backlightSettings.off_after,
});
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