11package kvm
22
33import (
4+ "errors"
45 "fmt"
56 "log"
7+ "os"
8+ "strconv"
69 "time"
710)
811
912var 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
1125func 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+
86250func 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}
0 commit comments