mirror of https://github.com/jetkvm/kvm.git
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:
parent
951173ba19
commit
5217377175
30
config.go
30
config.go
|
@ -12,24 +12,30 @@ type WakeOnLanDevice struct {
|
|||
}
|
||||
|
||||
type Config struct {
|
||||
CloudURL string `json:"cloud_url"`
|
||||
CloudToken string `json:"cloud_token"`
|
||||
GoogleIdentity string `json:"google_identity"`
|
||||
JigglerEnabled bool `json:"jiggler_enabled"`
|
||||
AutoUpdateEnabled bool `json:"auto_update_enabled"`
|
||||
IncludePreRelease bool `json:"include_pre_release"`
|
||||
HashedPassword string `json:"hashed_password"`
|
||||
LocalAuthToken string `json:"local_auth_token"`
|
||||
LocalAuthMode string `json:"localAuthMode"` //TODO: fix it with migration
|
||||
WakeOnLanDevices []WakeOnLanDevice `json:"wake_on_lan_devices"`
|
||||
CloudURL string `json:"cloud_url"`
|
||||
CloudToken string `json:"cloud_token"`
|
||||
GoogleIdentity string `json:"google_identity"`
|
||||
JigglerEnabled bool `json:"jiggler_enabled"`
|
||||
AutoUpdateEnabled bool `json:"auto_update_enabled"`
|
||||
IncludePreRelease bool `json:"include_pre_release"`
|
||||
HashedPassword string `json:"hashed_password"`
|
||||
LocalAuthToken string `json:"local_auth_token"`
|
||||
LocalAuthMode string `json:"localAuthMode"` //TODO: fix it with migration
|
||||
WakeOnLanDevices []WakeOnLanDevice `json:"wake_on_lan_devices"`
|
||||
EdidString string `json:"hdmi_edid_string"`
|
||||
DisplayMaxBrightness int `json:"display_max_brightness"`
|
||||
DisplayDimAfterSec int `json:"display_dim_after_sec"`
|
||||
DisplayOffAfterSec int `json:"display_off_after_sec"`
|
||||
}
|
||||
|
||||
const configPath = "/userdata/kvm_config.json"
|
||||
|
||||
var defaultConfig = &Config{
|
||||
CloudURL: "https://api.jetkvm.com",
|
||||
AutoUpdateEnabled: true, // Set a default value
|
||||
CloudURL: "https://api.jetkvm.com",
|
||||
AutoUpdateEnabled: true, // Set a default value
|
||||
DisplayMaxBrightness: 64,
|
||||
DisplayDimAfterSec: 120, // 2 minutes
|
||||
DisplayOffAfterSec: 1800, // 30 minutes
|
||||
}
|
||||
|
||||
var config *Config
|
||||
|
|
169
display.go
169
display.go
|
@ -1,12 +1,26 @@
|
|||
package kvm
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
var currentScreen = "ui_Boot_Screen"
|
||||
var backlightState = 0 // 0 - NORMAL, 1 - DIMMED, 2 - OFF
|
||||
|
||||
var (
|
||||
dimTicker *time.Ticker
|
||||
offTicker *time.Ticker
|
||||
)
|
||||
|
||||
const (
|
||||
touchscreenDevice string = "/dev/input/event1"
|
||||
backlightControlClass string = "/sys/class/backlight/backlight/brightness"
|
||||
)
|
||||
|
||||
func switchToScreen(screen string) {
|
||||
_, err := CallCtrlAction("lv_scr_load", map[string]interface{}{"obj": screen})
|
||||
|
@ -65,6 +79,7 @@ func requestDisplayUpdate() {
|
|||
return
|
||||
}
|
||||
go func() {
|
||||
wakeDisplay(false)
|
||||
fmt.Println("display updating........................")
|
||||
//TODO: only run once regardless how many pending updates
|
||||
updateDisplay()
|
||||
|
@ -83,6 +98,156 @@ func updateStaticContents() {
|
|||
updateLabelIfChanged("ui_Status_Content_Device_Id_Content_Label", GetDeviceID())
|
||||
}
|
||||
|
||||
// setDisplayBrightness sets /sys/class/backlight/backlight/brightness to alter
|
||||
// the backlight brightness of the JetKVM hardware's display.
|
||||
func setDisplayBrightness(brightness int) error {
|
||||
// NOTE: The actual maximum value for this is 255, but out-of-the-box, the value is set to 64.
|
||||
// The maximum set here is set to 100 to reduce the risk of drawing too much power (and besides, 255 is very bright!).
|
||||
if brightness > 100 || brightness < 0 {
|
||||
return errors.New("brightness value out of bounds, must be between 0 and 100")
|
||||
}
|
||||
|
||||
// Check the display backlight class is available
|
||||
if _, err := os.Stat(backlightControlClass); errors.Is(err, os.ErrNotExist) {
|
||||
return errors.New("brightness value cannot be set, possibly not running on JetKVM hardware")
|
||||
}
|
||||
|
||||
// Set the value
|
||||
bs := []byte(strconv.Itoa(brightness))
|
||||
err := os.WriteFile(backlightControlClass, bs, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("display: set brightness to %v\n", brightness)
|
||||
return nil
|
||||
}
|
||||
|
||||
// tick_displayDim() is called when when dim ticker expires, it simply reduces the brightness
|
||||
// of the display by half of the max brightness.
|
||||
func tick_displayDim() {
|
||||
err := setDisplayBrightness(config.DisplayMaxBrightness / 2)
|
||||
if err != nil {
|
||||
fmt.Printf("display: failed to dim display: %s\n", err)
|
||||
}
|
||||
|
||||
dimTicker.Stop()
|
||||
|
||||
backlightState = 1
|
||||
}
|
||||
|
||||
// tick_displayOff() is called when the off ticker expires, it turns off the display
|
||||
// by setting the brightness to zero.
|
||||
func tick_displayOff() {
|
||||
err := setDisplayBrightness(0)
|
||||
if err != nil {
|
||||
fmt.Printf("display: failed to turn off display: %s\n", err)
|
||||
}
|
||||
|
||||
offTicker.Stop()
|
||||
|
||||
backlightState = 2
|
||||
}
|
||||
|
||||
// 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.
|
||||
// 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
|
||||
}
|
||||
|
||||
// Don't try to wake up if the display is turned off.
|
||||
if config.DisplayMaxBrightness == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
err := setDisplayBrightness(config.DisplayMaxBrightness)
|
||||
if err != nil {
|
||||
fmt.Printf("display wake failed, %s\n", err)
|
||||
}
|
||||
|
||||
if config.DisplayDimAfterSec != 0 {
|
||||
dimTicker.Reset(time.Duration(config.DisplayDimAfterSec) * time.Second)
|
||||
}
|
||||
|
||||
if config.DisplayOffAfterSec != 0 {
|
||||
offTicker.Reset(time.Duration(config.DisplayOffAfterSec) * time.Second)
|
||||
}
|
||||
backlightState = 0
|
||||
}
|
||||
|
||||
// watchTsEvents monitors the touchscreen for events and simply calls wakeDisplay() to ensure the
|
||||
// touchscreen interface still works even with LCD dimming/off.
|
||||
// TODO: This is quite a hack, really we should be getting an event from jetkvm_native, or the whole display backlight
|
||||
// control should be hoisted up to jetkvm_native.
|
||||
func watchTsEvents() {
|
||||
ts, err := os.OpenFile(touchscreenDevice, os.O_RDONLY, 0666)
|
||||
if err != nil {
|
||||
fmt.Printf("display: failed to open touchscreen device: %s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
defer ts.Close()
|
||||
|
||||
// This buffer is set to 24 bytes as that's the normal size of events on /dev/input
|
||||
// Reference: https://www.kernel.org/doc/Documentation/input/input.txt
|
||||
// This could potentially be set higher, to require multiple events to wake the display.
|
||||
buf := make([]byte, 24)
|
||||
for {
|
||||
_, err := ts.Read(buf)
|
||||
if err != nil {
|
||||
fmt.Printf("display: failed to read from touchscreen device: %s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
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()
|
||||
// Don't start the tickers if the display is switched off.
|
||||
// Set the display to off if that's the case.
|
||||
if config.DisplayMaxBrightness == 0 {
|
||||
setDisplayBrightness(0)
|
||||
return
|
||||
}
|
||||
|
||||
if dimTicker == nil && config.DisplayDimAfterSec != 0 {
|
||||
fmt.Printf("display: dim_ticker has started\n")
|
||||
dimTicker = time.NewTicker(time.Duration(config.DisplayDimAfterSec) * time.Second)
|
||||
defer dimTicker.Stop()
|
||||
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-dimTicker.C:
|
||||
tick_displayDim()
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
if offTicker == nil && config.DisplayOffAfterSec != 0 {
|
||||
fmt.Printf("display: off_ticker has started\n")
|
||||
offTicker = time.NewTicker(time.Duration(config.DisplayOffAfterSec) * time.Second)
|
||||
defer offTicker.Stop()
|
||||
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-offTicker.C:
|
||||
tick_displayOff()
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
go func() {
|
||||
waitCtrlClientConnected()
|
||||
|
@ -91,6 +256,10 @@ func init() {
|
|||
updateStaticContents()
|
||||
displayInited = true
|
||||
fmt.Println("display inited")
|
||||
startBacklightTickers()
|
||||
wakeDisplay(true)
|
||||
requestDisplayUpdate()
|
||||
}()
|
||||
|
||||
go watchTsEvents()
|
||||
}
|
||||
|
|
60
jsonrpc.go
60
jsonrpc.go
|
@ -34,6 +34,12 @@ type JSONRPCEvent struct {
|
|||
Params interface{} `json:"params,omitempty"`
|
||||
}
|
||||
|
||||
type BacklightSettings struct {
|
||||
MaxBrightness int `json:"max_brightness"`
|
||||
DimAfter int `json:"dim_after"`
|
||||
OffAfter int `json:"off_after"`
|
||||
}
|
||||
|
||||
func writeJSONRPCResponse(response JSONRPCResponse, session *Session) {
|
||||
responseBytes, err := json.Marshal(response)
|
||||
if err != nil {
|
||||
|
@ -225,6 +231,56 @@ func rpcTryUpdate() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func rpcSetBacklightSettings(params BacklightSettings) error {
|
||||
LoadConfig()
|
||||
|
||||
blConfig := params
|
||||
|
||||
// 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 {
|
||||
return fmt.Errorf("dimAfter must be a positive integer")
|
||||
}
|
||||
|
||||
if blConfig.OffAfter < 0 {
|
||||
return fmt.Errorf("offAfter must be a positive integer")
|
||||
}
|
||||
|
||||
config.DisplayMaxBrightness = blConfig.MaxBrightness
|
||||
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
|
||||
}
|
||||
|
||||
func rpcGetBacklightSettings() (*BacklightSettings, error) {
|
||||
LoadConfig()
|
||||
|
||||
return &BacklightSettings{
|
||||
MaxBrightness: config.DisplayMaxBrightness,
|
||||
DimAfter: int(config.DisplayDimAfterSec),
|
||||
OffAfter: int(config.DisplayOffAfterSec),
|
||||
}, nil
|
||||
}
|
||||
|
||||
const (
|
||||
devModeFile = "/userdata/jetkvm/devmode.enable"
|
||||
sshKeyDir = "/userdata/dropbear/.ssh"
|
||||
|
@ -385,7 +441,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)
|
||||
|
@ -560,4 +616,6 @@ var rpcHandlers = map[string]RPCHandler{
|
|||
"getWakeOnLanDevices": {Func: rpcGetWakeOnLanDevices},
|
||||
"setWakeOnLanDevices": {Func: rpcSetWakeOnLanDevices, Params: []string{"params"}},
|
||||
"resetConfig": {Func: rpcResetConfig},
|
||||
"setBacklightSettings": {Func: rpcSetBacklightSettings, Params: []string{"params"}},
|
||||
"getBacklightSettings": {Func: rpcGetBacklightSettings},
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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",
|
||||
|
|
Loading…
Reference in New Issue