Skip to content

Commit 99b3017

Browse files
authored
Release 0.3.6
2 parents 1d6b7ad + 2ce327e commit 99b3017

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+2290
-588
lines changed

Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
VERSION_DEV := 0.3.6-dev$(shell date +%Y%m%d%H%M)
2-
VERSION := 0.3.5
1+
VERSION_DEV := 0.3.7-dev$(shell date +%Y%m%d%H%M)
2+
VERSION := 0.3.6
33

44
hash_resource:
55
@shasum -a 256 resource/jetkvm_native | cut -d ' ' -f 1 > resource/jetkvm_native.sha256

config.go

Lines changed: 32 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"encoding/json"
55
"fmt"
66
"os"
7+
"sync"
78
)
89

910
type WakeOnLanDevice struct {
@@ -12,51 +13,66 @@ type WakeOnLanDevice struct {
1213
}
1314

1415
type Config struct {
15-
CloudURL string `json:"cloud_url"`
16-
CloudToken string `json:"cloud_token"`
17-
GoogleIdentity string `json:"google_identity"`
18-
JigglerEnabled bool `json:"jiggler_enabled"`
19-
AutoUpdateEnabled bool `json:"auto_update_enabled"`
20-
IncludePreRelease bool `json:"include_pre_release"`
21-
HashedPassword string `json:"hashed_password"`
22-
LocalAuthToken string `json:"local_auth_token"`
23-
LocalAuthMode string `json:"localAuthMode"` //TODO: fix it with migration
24-
WakeOnLanDevices []WakeOnLanDevice `json:"wake_on_lan_devices"`
16+
CloudURL string `json:"cloud_url"`
17+
CloudToken string `json:"cloud_token"`
18+
GoogleIdentity string `json:"google_identity"`
19+
JigglerEnabled bool `json:"jiggler_enabled"`
20+
AutoUpdateEnabled bool `json:"auto_update_enabled"`
21+
IncludePreRelease bool `json:"include_pre_release"`
22+
HashedPassword string `json:"hashed_password"`
23+
LocalAuthToken string `json:"local_auth_token"`
24+
LocalAuthMode string `json:"localAuthMode"` //TODO: fix it with migration
25+
WakeOnLanDevices []WakeOnLanDevice `json:"wake_on_lan_devices"`
26+
EdidString string `json:"hdmi_edid_string"`
27+
ActiveExtension string `json:"active_extension"`
28+
DisplayMaxBrightness int `json:"display_max_brightness"`
29+
DisplayDimAfterSec int `json:"display_dim_after_sec"`
30+
DisplayOffAfterSec int `json:"display_off_after_sec"`
2531
}
2632

2733
const configPath = "/userdata/kvm_config.json"
2834

2935
var defaultConfig = &Config{
30-
CloudURL: "https://api.jetkvm.com",
31-
AutoUpdateEnabled: true, // Set a default value
36+
CloudURL: "https://api.jetkvm.com",
37+
AutoUpdateEnabled: true, // Set a default value
38+
ActiveExtension: "",
39+
DisplayMaxBrightness: 64,
40+
DisplayDimAfterSec: 120, // 2 minutes
41+
DisplayOffAfterSec: 1800, // 30 minutes
3242
}
3343

34-
var config *Config
44+
var (
45+
config *Config
46+
configLock = &sync.Mutex{}
47+
)
3548

3649
func LoadConfig() {
3750
if config != nil {
51+
logger.Info("config already loaded, skipping")
3852
return
3953
}
4054

4155
file, err := os.Open(configPath)
4256
if err != nil {
4357
logger.Debug("default config file doesn't exist, using default")
44-
config = defaultConfig
4558
return
4659
}
4760
defer file.Close()
4861

49-
var loadedConfig Config
62+
// load and merge the default config with the user config
63+
loadedConfig := *defaultConfig
5064
if err := json.NewDecoder(file).Decode(&loadedConfig); err != nil {
5165
logger.Errorf("config file JSON parsing failed, %v", err)
52-
config = defaultConfig
5366
return
5467
}
5568

5669
config = &loadedConfig
5770
}
5871

5972
func SaveConfig() error {
73+
configLock.Lock()
74+
defer configLock.Unlock()
75+
6076
file, err := os.Create(configPath)
6177
if err != nil {
6278
return fmt.Errorf("failed to create config file: %w", err)

display.go

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,26 @@
11
package kvm
22

33
import (
4+
"errors"
45
"fmt"
56
"log"
7+
"os"
8+
"strconv"
69
"time"
710
)
811

912
var currentScreen = "ui_Boot_Screen"
13+
var backlightState = 0 // 0 - NORMAL, 1 - DIMMED, 2 - OFF
14+
15+
var (
16+
dimTicker *time.Ticker
17+
offTicker *time.Ticker
18+
)
19+
20+
const (
21+
touchscreenDevice string = "/dev/input/event1"
22+
backlightControlClass string = "/sys/class/backlight/backlight/brightness"
23+
)
1024

1125
func switchToScreen(screen string) {
1226
_, err := CallCtrlAction("lv_scr_load", map[string]interface{}{"obj": screen})
@@ -65,6 +79,7 @@ func requestDisplayUpdate() {
6579
return
6680
}
6781
go func() {
82+
wakeDisplay(false)
6883
fmt.Println("display updating........................")
6984
//TODO: only run once regardless how many pending updates
7085
updateDisplay()
@@ -83,6 +98,155 @@ func updateStaticContents() {
8398
updateLabelIfChanged("ui_Status_Content_Device_Id_Content_Label", GetDeviceID())
8499
}
85100

101+
// setDisplayBrightness sets /sys/class/backlight/backlight/brightness to alter
102+
// the backlight brightness of the JetKVM hardware's display.
103+
func setDisplayBrightness(brightness int) error {
104+
// NOTE: The actual maximum value for this is 255, but out-of-the-box, the value is set to 64.
105+
// The maximum set here is set to 100 to reduce the risk of drawing too much power (and besides, 255 is very bright!).
106+
if brightness > 100 || brightness < 0 {
107+
return errors.New("brightness value out of bounds, must be between 0 and 100")
108+
}
109+
110+
// Check the display backlight class is available
111+
if _, err := os.Stat(backlightControlClass); errors.Is(err, os.ErrNotExist) {
112+
return errors.New("brightness value cannot be set, possibly not running on JetKVM hardware")
113+
}
114+
115+
// Set the value
116+
bs := []byte(strconv.Itoa(brightness))
117+
err := os.WriteFile(backlightControlClass, bs, 0644)
118+
if err != nil {
119+
return err
120+
}
121+
122+
fmt.Printf("display: set brightness to %v\n", brightness)
123+
return nil
124+
}
125+
126+
// tick_displayDim() is called when when dim ticker expires, it simply reduces the brightness
127+
// of the display by half of the max brightness.
128+
func tick_displayDim() {
129+
err := setDisplayBrightness(config.DisplayMaxBrightness / 2)
130+
if err != nil {
131+
fmt.Printf("display: failed to dim display: %s\n", err)
132+
}
133+
134+
dimTicker.Stop()
135+
136+
backlightState = 1
137+
}
138+
139+
// tick_displayOff() is called when the off ticker expires, it turns off the display
140+
// by setting the brightness to zero.
141+
func tick_displayOff() {
142+
err := setDisplayBrightness(0)
143+
if err != nil {
144+
fmt.Printf("display: failed to turn off display: %s\n", err)
145+
}
146+
147+
offTicker.Stop()
148+
149+
backlightState = 2
150+
}
151+
152+
// wakeDisplay sets the display brightness back to config.DisplayMaxBrightness and stores the time the display
153+
// last woke, ready for displayTimeoutTick to put the display back in the dim/off states.
154+
// Set force to true to skip the backlight state check, this should be done if altering the tickers.
155+
func wakeDisplay(force bool) {
156+
if backlightState == 0 && !force {
157+
return
158+
}
159+
160+
// Don't try to wake up if the display is turned off.
161+
if config.DisplayMaxBrightness == 0 {
162+
return
163+
}
164+
165+
err := setDisplayBrightness(config.DisplayMaxBrightness)
166+
if err != nil {
167+
fmt.Printf("display wake failed, %s\n", err)
168+
}
169+
170+
if config.DisplayDimAfterSec != 0 {
171+
dimTicker.Reset(time.Duration(config.DisplayDimAfterSec) * time.Second)
172+
}
173+
174+
if config.DisplayOffAfterSec != 0 {
175+
offTicker.Reset(time.Duration(config.DisplayOffAfterSec) * time.Second)
176+
}
177+
backlightState = 0
178+
}
179+
180+
// watchTsEvents monitors the touchscreen for events and simply calls wakeDisplay() to ensure the
181+
// touchscreen interface still works even with LCD dimming/off.
182+
// TODO: This is quite a hack, really we should be getting an event from jetkvm_native, or the whole display backlight
183+
// control should be hoisted up to jetkvm_native.
184+
func watchTsEvents() {
185+
ts, err := os.OpenFile(touchscreenDevice, os.O_RDONLY, 0666)
186+
if err != nil {
187+
fmt.Printf("display: failed to open touchscreen device: %s\n", err)
188+
return
189+
}
190+
191+
defer ts.Close()
192+
193+
// This buffer is set to 24 bytes as that's the normal size of events on /dev/input
194+
// Reference: https://www.kernel.org/doc/Documentation/input/input.txt
195+
// This could potentially be set higher, to require multiple events to wake the display.
196+
buf := make([]byte, 24)
197+
for {
198+
_, err := ts.Read(buf)
199+
if err != nil {
200+
fmt.Printf("display: failed to read from touchscreen device: %s\n", err)
201+
return
202+
}
203+
204+
wakeDisplay(false)
205+
}
206+
}
207+
208+
// startBacklightTickers starts the two tickers for dimming and switching off the display
209+
// if they're not already set. This is done separately to the init routine as the "never dim"
210+
// option has the value set to zero, but time.NewTicker only accept positive values.
211+
func startBacklightTickers() {
212+
// Don't start the tickers if the display is switched off.
213+
// Set the display to off if that's the case.
214+
if config.DisplayMaxBrightness == 0 {
215+
setDisplayBrightness(0)
216+
return
217+
}
218+
219+
if dimTicker == nil && config.DisplayDimAfterSec != 0 {
220+
fmt.Printf("display: dim_ticker has started\n")
221+
dimTicker = time.NewTicker(time.Duration(config.DisplayDimAfterSec) * time.Second)
222+
defer dimTicker.Stop()
223+
224+
go func() {
225+
for {
226+
select {
227+
case <-dimTicker.C:
228+
tick_displayDim()
229+
}
230+
}
231+
}()
232+
}
233+
234+
if offTicker == nil && config.DisplayOffAfterSec != 0 {
235+
fmt.Printf("display: off_ticker has started\n")
236+
offTicker = time.NewTicker(time.Duration(config.DisplayOffAfterSec) * time.Second)
237+
defer offTicker.Stop()
238+
239+
go func() {
240+
for {
241+
select {
242+
case <-offTicker.C:
243+
tick_displayOff()
244+
}
245+
}
246+
}()
247+
}
248+
}
249+
86250
func init() {
87251
go func() {
88252
waitCtrlClientConnected()
@@ -91,6 +255,10 @@ func init() {
91255
updateStaticContents()
92256
displayInited = true
93257
fmt.Println("display inited")
258+
startBacklightTickers()
259+
wakeDisplay(true)
94260
requestDisplayUpdate()
95261
}()
262+
263+
go watchTsEvents()
96264
}

go.mod

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,15 @@ require (
1414
github.com/google/uuid v1.6.0
1515
github.com/gwatts/rootcerts v0.0.0-20240401182218-3ab9db955caf
1616
github.com/hanwen/go-fuse/v2 v2.5.1
17+
github.com/hashicorp/go-envparse v0.1.0
1718
github.com/openstadia/go-usb-gadget v0.0.0-20231115171102-aebd56bbb965
1819
github.com/pion/logging v0.2.2
1920
github.com/pion/mdns/v2 v2.0.7
2021
github.com/pion/webrtc/v4 v4.0.0
2122
github.com/pojntfx/go-nbd v0.3.2
2223
github.com/psanford/httpreadat v0.1.0
2324
github.com/vishvananda/netlink v1.3.0
25+
go.bug.st/serial v1.6.2
2426
golang.org/x/crypto v0.28.0
2527
golang.org/x/net v0.30.0
2628
)
@@ -32,6 +34,7 @@ require (
3234
github.com/bytedance/sonic/loader v0.1.1 // indirect
3335
github.com/cloudwego/base64x v0.1.4 // indirect
3436
github.com/cloudwego/iasm v0.2.0 // indirect
37+
github.com/creack/goselect v0.1.2 // indirect
3538
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
3639
github.com/gin-contrib/sse v0.1.0 // indirect
3740
github.com/go-jose/go-jose/v4 v4.0.2 // indirect
@@ -68,7 +71,7 @@ require (
6871
github.com/wlynxg/anet v0.0.5 // indirect
6972
golang.org/x/arch v0.8.0 // indirect
7073
golang.org/x/oauth2 v0.21.0 // indirect
71-
golang.org/x/sys v0.26.0 // indirect
74+
golang.org/x/sys v0.29.0 // indirect
7275
golang.org/x/text v0.19.0 // indirect
7376
google.golang.org/protobuf v1.34.0 // indirect
7477
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect

go.sum

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ github.com/coder/websocket v1.8.12 h1:5bUXkEPPIbewrnkU8LTCLVaxi4N4J8ahufH2vlo4NA
1616
github.com/coder/websocket v1.8.12/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs=
1717
github.com/coreos/go-oidc/v3 v3.11.0 h1:Ia3MxdwpSw702YW0xgfmP1GVCMA9aEFWu12XUZ3/OtI=
1818
github.com/coreos/go-oidc/v3 v3.11.0/go.mod h1:gE3LgjOgFoHi9a4ce4/tJczr0Ai2/BoDhf0r5lltWI0=
19+
github.com/creack/goselect v0.1.2 h1:2DNy14+JPjRBgPzAd1thbQp4BSIihxcBf0IXhQXDRa0=
20+
github.com/creack/goselect v0.1.2/go.mod h1:a/NhLweNvqIYMuxcMOuWY516Cimucms3DglDzQP3hKY=
1921
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
2022
github.com/creack/pty v1.1.23 h1:4M6+isWdcStXEf15G/RbrMPOQj1dZ7HPZCGwE4kOeP0=
2123
github.com/creack/pty v1.1.23/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
@@ -49,6 +51,8 @@ github.com/gwatts/rootcerts v0.0.0-20240401182218-3ab9db955caf h1:JO6ISZIvEUitto
4951
github.com/gwatts/rootcerts v0.0.0-20240401182218-3ab9db955caf/go.mod h1:5Kt9XkWvkGi2OHOq0QsGxebHmhCcqJ8KCbNg/a6+n+g=
5052
github.com/hanwen/go-fuse/v2 v2.5.1 h1:OQBE8zVemSocRxA4OaFJbjJ5hlpCmIWbGr7r0M4uoQQ=
5153
github.com/hanwen/go-fuse/v2 v2.5.1/go.mod h1:xKwi1cF7nXAOBCXujD5ie0ZKsxc8GGSA1rlMJc+8IJs=
54+
github.com/hashicorp/go-envparse v0.1.0 h1:bE++6bhIsNCPLvgDZkYqo3nA+/PFI51pkrHdmPSDFPY=
55+
github.com/hashicorp/go-envparse v0.1.0/go.mod h1:OHheN1GoygLlAkTlXLXvAdnXdZxy8JUweQ1rAXx1xnc=
5256
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
5357
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
5458
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
@@ -144,6 +148,8 @@ github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1Y
144148
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
145149
github.com/wlynxg/anet v0.0.5 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU=
146150
github.com/wlynxg/anet v0.0.5/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA=
151+
go.bug.st/serial v1.6.2 h1:kn9LRX3sdm+WxWKufMlIRndwGfPWsH1/9lCWXQCasq8=
152+
go.bug.st/serial v1.6.2/go.mod h1:UABfsluHAiaNI+La2iESysd9Vetq7VRdpxvjx7CmmOE=
147153
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
148154
golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
149155
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
@@ -159,8 +165,8 @@ golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
159165
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
160166
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
161167
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
162-
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
163-
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
168+
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
169+
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
164170
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
165171
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
166172
google.golang.org/protobuf v1.34.0 h1:Qo/qEd2RZPCf2nKuorzksSknv0d3ERwp1vFG38gSmH4=

0 commit comments

Comments
 (0)