diff --git a/config.go b/config.go index 86c64e5..a1d224a 100644 --- a/config.go +++ b/config.go @@ -90,6 +90,7 @@ type Config struct { KeyboardLayout string `json:"keyboard_layout"` EdidString string `json:"hdmi_edid_string"` ActiveExtension string `json:"active_extension"` + DisplayRotation string `json:"display_rotation"` DisplayMaxBrightness int `json:"display_max_brightness"` DisplayDimAfterSec int `json:"display_dim_after_sec"` DisplayOffAfterSec int `json:"display_off_after_sec"` @@ -109,6 +110,7 @@ var defaultConfig = &Config{ ActiveExtension: "", KeyboardMacros: []KeyboardMacro{}, KeyboardLayout: "en_US", + DisplayRotation: "270", DisplayMaxBrightness: 64, DisplayDimAfterSec: 120, // 2 minutes DisplayOffAfterSec: 1800, // 30 minutes diff --git a/display.go b/display.go index e2e82e1..f4d2a94 100644 --- a/display.go +++ b/display.go @@ -73,6 +73,10 @@ func lvImgSetSrc(objName string, src string) (*CtrlResponse, error) { return CallCtrlAction("lv_img_set_src", map[string]interface{}{"obj": objName, "src": src}) } +func lvDispSetRotation(rotation string) (*CtrlResponse, error) { + return CallCtrlAction("lv_disp_set_rotation", map[string]interface{}{"rotation": rotation}) +} + func updateLabelIfChanged(objName string, newText string) { if newText != "" && newText != displayedTexts[objName] { _, _ = lvLabelSetText(objName, newText) @@ -373,6 +377,7 @@ func init() { waitCtrlClientConnected() displayLogger.Info().Msg("setting initial display contents") time.Sleep(500 * time.Millisecond) + _, _ = lvDispSetRotation(config.DisplayRotation) updateStaticContents() displayInited = true displayLogger.Info().Msg("display inited") diff --git a/internal/timesync/ntp.go b/internal/timesync/ntp.go index 41656b7..d45112c 100644 --- a/internal/timesync/ntp.go +++ b/internal/timesync/ntp.go @@ -13,7 +13,8 @@ var defaultNTPServers = []string{ "time.aws.com", "time.windows.com", "time.google.com", - "162.159.200.123", // time.cloudflare.com + "162.159.200.123", // time.cloudflare.com IPv4 + "2606:4700:f1::123", // time.cloudflare.com IPv6 "0.pool.ntp.org", "1.pool.ntp.org", "2.pool.ntp.org", @@ -57,6 +58,13 @@ func (t *TimeSync) queryMultipleNTP(servers []string, timeout time.Duration) (no // query the server now, response, err := queryNtpServer(server, timeout) + if err != nil { + scopedLogger.Warn(). + Str("error", err.Error()). + Msg("failed to query NTP server") + results <- nil + return + } // set the last RTT metricNtpServerLastRTT.WithLabelValues( @@ -76,32 +84,33 @@ func (t *TimeSync) queryMultipleNTP(servers []string, timeout time.Duration) (no strconv.Itoa(int(response.Precision)), ).Set(1) - if err == nil { - // increase success count - metricNtpTotalSuccessCount.Inc() - metricNtpSuccessCount.WithLabelValues(server).Inc() + // increase success count + metricNtpTotalSuccessCount.Inc() + metricNtpSuccessCount.WithLabelValues(server).Inc() - scopedLogger.Info(). - Str("time", now.Format(time.RFC3339)). - Str("reference", response.ReferenceString()). - Str("rtt", response.RTT.String()). - Str("clockOffset", response.ClockOffset.String()). - Uint8("stratum", response.Stratum). - Msg("NTP server returned time") - results <- &ntpResult{ - now: now, - offset: &response.ClockOffset, - } - } else { - scopedLogger.Warn(). - Str("error", err.Error()). - Msg("failed to query NTP server") + scopedLogger.Info(). + Str("time", now.Format(time.RFC3339)). + Str("reference", response.ReferenceString()). + Str("rtt", response.RTT.String()). + Str("clockOffset", response.ClockOffset.String()). + Uint8("stratum", response.Stratum). + Msg("NTP server returned time") + results <- &ntpResult{ + now: now, + offset: &response.ClockOffset, } }(server) } - result := <-results - return result.now, result.offset + for range servers { + result := <-results + if result == nil { + continue + } + now, offset = result.now, result.offset + return + } + return } func queryNtpServer(server string, timeout time.Duration) (now *time.Time, response *ntp.Response, err error) { diff --git a/internal/usbgadget/hid_mouse_absolute.go b/internal/usbgadget/hid_mouse_absolute.go index de77b1e..6629caa 100644 --- a/internal/usbgadget/hid_mouse_absolute.go +++ b/internal/usbgadget/hid_mouse_absolute.go @@ -55,6 +55,8 @@ var absoluteMouseCombinedReportDesc = []byte{ 0x09, 0x38, // Usage (Wheel) 0x15, 0x81, // Logical Minimum (-127) 0x25, 0x7F, // Logical Maximum (127) + 0x35, 0x00, // Physical Minimum (0) = Reset Physical Minimum + 0x45, 0x00, // Physical Maximum (0) = Reset Physical Maximum 0x75, 0x08, // Report Size (8) 0x95, 0x01, // Report Count (1) 0x81, 0x06, // Input (Data, Var, Rel) diff --git a/jsonrpc.go b/jsonrpc.go index 13280d9..a92d75f 100644 --- a/jsonrpc.go +++ b/jsonrpc.go @@ -38,6 +38,10 @@ type JSONRPCEvent struct { Params interface{} `json:"params,omitempty"` } +type DisplayRotationSettings struct { + Rotation string `json:"rotation"` +} + type BacklightSettings struct { MaxBrightness int `json:"max_brightness"` DimAfter int `json:"dim_after"` @@ -280,6 +284,24 @@ func rpcTryUpdate() error { return nil } +func rpcSetDisplayRotation(params DisplayRotationSettings) error { + var err error + _, err = lvDispSetRotation(params.Rotation) + if err == nil { + config.DisplayRotation = params.Rotation + if err := SaveConfig(); err != nil { + return fmt.Errorf("failed to save config: %w", err) + } + } + return err +} + +func rpcGetDisplayRotation() (*DisplayRotationSettings, error) { + return &DisplayRotationSettings{ + Rotation: config.DisplayRotation, + }, nil +} + func rpcSetBacklightSettings(params BacklightSettings) error { blConfig := params @@ -1024,6 +1046,8 @@ var rpcHandlers = map[string]RPCHandler{ "getWakeOnLanDevices": {Func: rpcGetWakeOnLanDevices}, "setWakeOnLanDevices": {Func: rpcSetWakeOnLanDevices, Params: []string{"params"}}, "resetConfig": {Func: rpcResetConfig}, + "setDisplayRotation": {Func: rpcSetDisplayRotation, Params: []string{"params"}}, + "getDisplayRotation": {Func: rpcGetDisplayRotation}, "setBacklightSettings": {Func: rpcSetBacklightSettings, Params: []string{"params"}}, "getBacklightSettings": {Func: rpcGetBacklightSettings}, "getDCPowerState": {Func: rpcGetDCPowerState}, diff --git a/ui/src/hooks/stores.ts b/ui/src/hooks/stores.ts index 14b8bb1..cdd8c9f 100644 --- a/ui/src/hooks/stores.ts +++ b/ui/src/hooks/stores.ts @@ -292,6 +292,9 @@ interface SettingsState { developerMode: boolean; setDeveloperMode: (enabled: boolean) => void; + displayRotation: string; + setDisplayRotation: (rotation: string) => void; + backlightSettings: BacklightSettings; setBacklightSettings: (settings: BacklightSettings) => void; } @@ -312,6 +315,10 @@ export const useSettingsStore = create( developerMode: false, setDeveloperMode: enabled => set({ developerMode: enabled }), + displayRotation: "270", + setDisplayRotation: (rotation: string) => + set({ displayRotation: rotation }), + backlightSettings: { max_brightness: 100, dim_after: 10000, diff --git a/ui/src/routes/devices.$id.settings.hardware.tsx b/ui/src/routes/devices.$id.settings.hardware.tsx index 9fde3e3..82cc6a1 100644 --- a/ui/src/routes/devices.$id.settings.hardware.tsx +++ b/ui/src/routes/devices.$id.settings.hardware.tsx @@ -15,6 +15,25 @@ export default function SettingsHardwareRoute() { const [send] = useJsonRpc(); const settings = useSettingsStore(); + const setDisplayRotation = useSettingsStore(state => state.setDisplayRotation); + + const handleDisplayRotationChange = (rotation: string) => { + setDisplayRotation(rotation); + handleDisplayRotationSave(); + }; + + const handleDisplayRotationSave = () => { + send("setDisplayRotation", { params: { rotation: settings.displayRotation } }, resp => { + if ("error" in resp) { + notifications.error( + `Failed to set display orientation: ${resp.error.data || "Unknown error"}`, + ); + return; + } + notifications.success("Display orientation updated successfully"); + }); + }; + const setBacklightSettings = useSettingsStore(state => state.setBacklightSettings); const handleBacklightSettingsChange = (settings: BacklightSettings) => { @@ -59,6 +78,24 @@ export default function SettingsHardwareRoute() { description="Configure display settings and hardware options for your JetKVM device" />
+ + { + settings.displayRotation = e.target.value; + handleDisplayRotationChange(settings.displayRotation); + }} + /> +