mirror of https://github.com/jetkvm/kvm.git
Compare commits
7 Commits
a5c2f734a4
...
443aff5152
Author | SHA1 | Date |
---|---|---|
|
443aff5152 | |
|
584768bacf | |
|
488276f3a8 | |
|
7267347261 | |
|
393bc122d4 | |
|
6d13e1be12 | |
|
84e4b44df0 |
|
@ -30,8 +30,8 @@ var defaultGadgetConfig = map[string]gadgetConfigItem{
|
||||||
attrs: gadgetAttributes{
|
attrs: gadgetAttributes{
|
||||||
"bcdUSB": "0x0200", // USB 2.0
|
"bcdUSB": "0x0200", // USB 2.0
|
||||||
"idVendor": "0x1d6b", // The Linux Foundation
|
"idVendor": "0x1d6b", // The Linux Foundation
|
||||||
"idProduct": "0104", // Multifunction Composite Gadget
|
"idProduct": "0x0104", // Multifunction Composite Gadget
|
||||||
"bcdDevice": "0100",
|
"bcdDevice": "0x0100", // USB2
|
||||||
},
|
},
|
||||||
configAttrs: gadgetAttributes{
|
configAttrs: gadgetAttributes{
|
||||||
"MaxPower": "250", // in unit of 2mA
|
"MaxPower": "250", // in unit of 2mA
|
||||||
|
|
19
jsonrpc.go
19
jsonrpc.go
|
@ -681,10 +681,11 @@ func rpcResetConfig() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
type DCPowerState struct {
|
type DCPowerState struct {
|
||||||
IsOn bool `json:"isOn"`
|
IsOn bool `json:"isOn"`
|
||||||
Voltage float64 `json:"voltage"`
|
Voltage float64 `json:"voltage"`
|
||||||
Current float64 `json:"current"`
|
Current float64 `json:"current"`
|
||||||
Power float64 `json:"power"`
|
Power float64 `json:"power"`
|
||||||
|
RestoreState int `json:"restoreState"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func rpcGetDCPowerState() (DCPowerState, error) {
|
func rpcGetDCPowerState() (DCPowerState, error) {
|
||||||
|
@ -700,6 +701,15 @@ func rpcSetDCPowerState(enabled bool) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func rpcSetDCRestoreState(state int) error {
|
||||||
|
logger.Info().Int("state", state).Msg("Setting DC restore state")
|
||||||
|
err := setDCRestoreState(state)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to set DC restore state: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func rpcGetActiveExtension() (string, error) {
|
func rpcGetActiveExtension() (string, error) {
|
||||||
return config.ActiveExtension, nil
|
return config.ActiveExtension, nil
|
||||||
}
|
}
|
||||||
|
@ -1088,6 +1098,7 @@ var rpcHandlers = map[string]RPCHandler{
|
||||||
"getBacklightSettings": {Func: rpcGetBacklightSettings},
|
"getBacklightSettings": {Func: rpcGetBacklightSettings},
|
||||||
"getDCPowerState": {Func: rpcGetDCPowerState},
|
"getDCPowerState": {Func: rpcGetDCPowerState},
|
||||||
"setDCPowerState": {Func: rpcSetDCPowerState, Params: []string{"enabled"}},
|
"setDCPowerState": {Func: rpcSetDCPowerState, Params: []string{"enabled"}},
|
||||||
|
"setDCRestoreState": {Func: rpcSetDCRestoreState, Params: []string{"state"}},
|
||||||
"getActiveExtension": {Func: rpcGetActiveExtension},
|
"getActiveExtension": {Func: rpcGetActiveExtension},
|
||||||
"setActiveExtension": {Func: rpcSetActiveExtension, Params: []string{"extensionId"}},
|
"setActiveExtension": {Func: rpcSetActiveExtension, Params: []string{"extensionId"}},
|
||||||
"getATXState": {Func: rpcGetATXState},
|
"getATXState": {Func: rpcGetATXState},
|
||||||
|
|
39
serial.go
39
serial.go
|
@ -142,6 +142,7 @@ var dcState DCPowerState
|
||||||
func runDCControl() {
|
func runDCControl() {
|
||||||
scopedLogger := serialLogger.With().Str("service", "dc_control").Logger()
|
scopedLogger := serialLogger.With().Str("service", "dc_control").Logger()
|
||||||
reader := bufio.NewReader(port)
|
reader := bufio.NewReader(port)
|
||||||
|
hasRestoreFeature := false
|
||||||
for {
|
for {
|
||||||
line, err := reader.ReadString('\n')
|
line, err := reader.ReadString('\n')
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -151,7 +152,13 @@ func runDCControl() {
|
||||||
|
|
||||||
// Split the line by semicolon
|
// Split the line by semicolon
|
||||||
parts := strings.Split(strings.TrimSpace(line), ";")
|
parts := strings.Split(strings.TrimSpace(line), ";")
|
||||||
if len(parts) != 4 {
|
if len(parts) == 5 {
|
||||||
|
scopedLogger.Debug().Str("line", line).Msg("Detected DC extension with restore feature")
|
||||||
|
hasRestoreFeature = true
|
||||||
|
} else if len(parts) == 4 {
|
||||||
|
scopedLogger.Debug().Str("line", line).Msg("Detected DC extension without restore feature")
|
||||||
|
hasRestoreFeature = false
|
||||||
|
} else {
|
||||||
scopedLogger.Warn().Str("line", line).Msg("Invalid line")
|
scopedLogger.Warn().Str("line", line).Msg("Invalid line")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -163,6 +170,17 @@ func runDCControl() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
dcState.IsOn = powerState == 1
|
dcState.IsOn = powerState == 1
|
||||||
|
if hasRestoreFeature {
|
||||||
|
restoreState, err := strconv.Atoi(parts[4])
|
||||||
|
if err != nil {
|
||||||
|
scopedLogger.Warn().Err(err).Msg("Invalid restore state")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
dcState.RestoreState = restoreState
|
||||||
|
} else {
|
||||||
|
// -1 means not supported
|
||||||
|
dcState.RestoreState = -1
|
||||||
|
}
|
||||||
milliVolts, err := strconv.ParseFloat(parts[1], 64)
|
milliVolts, err := strconv.ParseFloat(parts[1], 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
scopedLogger.Warn().Err(err).Msg("Invalid voltage")
|
scopedLogger.Warn().Err(err).Msg("Invalid voltage")
|
||||||
|
@ -210,6 +228,25 @@ func setDCPowerState(on bool) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func setDCRestoreState(state int) error {
|
||||||
|
_, err := port.Write([]byte("\n"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
command := "RESTORE_MODE_OFF\n"
|
||||||
|
switch state {
|
||||||
|
case 1:
|
||||||
|
command = "RESTORE_MODE_ON\n"
|
||||||
|
case 2:
|
||||||
|
command = "RESTORE_MODE_LAST_STATE\n"
|
||||||
|
}
|
||||||
|
_, err = port.Write([]byte(command))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
var defaultMode = &serial.Mode{
|
var defaultMode = &serial.Mode{
|
||||||
BaudRate: 115200,
|
BaudRate: 115200,
|
||||||
DataBits: 8,
|
DataBits: 8,
|
||||||
|
|
|
@ -262,23 +262,6 @@ export default function Actionbar({
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/* {useSettingsStore().actionBarCtrlAltDel && (
|
|
||||||
<div className="hidden lg:block">
|
|
||||||
<Button
|
|
||||||
size="XS"
|
|
||||||
theme="light"
|
|
||||||
text="Ctrl + Alt + Del"
|
|
||||||
LeadingIcon={FaLock}
|
|
||||||
onClick={() => {
|
|
||||||
sendKeyboardEvent(
|
|
||||||
[keys["Delete"]],
|
|
||||||
[modifiers["ControlLeft"], modifiers["AltLeft"]],
|
|
||||||
);
|
|
||||||
setTimeout(resetKeyboardState, 100);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)} */}
|
|
||||||
<div>
|
<div>
|
||||||
<Button
|
<Button
|
||||||
size="XS"
|
size="XS"
|
||||||
|
|
|
@ -41,6 +41,12 @@ export default function InfoBar() {
|
||||||
const keyboardLedStateSyncAvailable = useHidStore(state => state.keyboardLedStateSyncAvailable);
|
const keyboardLedStateSyncAvailable = useHidStore(state => state.keyboardLedStateSyncAvailable);
|
||||||
const keyboardLedSync = useSettingsStore(state => state.keyboardLedSync);
|
const keyboardLedSync = useSettingsStore(state => state.keyboardLedSync);
|
||||||
|
|
||||||
|
const isShiftActive = useHidStore(state => state.isShiftActive);
|
||||||
|
const isCtrlActive = useHidStore(state => state.isCtrlActive);
|
||||||
|
const isAltActive = useHidStore(state => state.isAltActive);
|
||||||
|
const isMetaActive = useHidStore(state => state.isMetaActive);
|
||||||
|
const isAltGrActive = useHidStore(state => state.isAltGrActive);
|
||||||
|
|
||||||
const isTurnServerInUse = useRTCStore(state => state.isTurnServerInUse);
|
const isTurnServerInUse = useRTCStore(state => state.isTurnServerInUse);
|
||||||
|
|
||||||
const usbState = useHidStore(state => state.usbState);
|
const usbState = useHidStore(state => state.usbState);
|
||||||
|
@ -135,6 +141,56 @@ export default function InfoBar() {
|
||||||
{keyboardLedSync === "browser" ? "Browser" : "Host"}
|
{keyboardLedSync === "browser" ? "Browser" : "Host"}
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
<div
|
||||||
|
className={cx(
|
||||||
|
"shrink-0 p-1 px-1.5 text-xs",
|
||||||
|
isShiftActive
|
||||||
|
? "text-black dark:text-white"
|
||||||
|
: "text-slate-800/20 dark:text-slate-300/20",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
Shift
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={cx(
|
||||||
|
"shrink-0 p-1 px-1.5 text-xs",
|
||||||
|
isCtrlActive
|
||||||
|
? "text-black dark:text-white"
|
||||||
|
: "text-slate-800/20 dark:text-slate-300/20",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
Ctrl
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={cx(
|
||||||
|
"shrink-0 p-1 px-1.5 text-xs",
|
||||||
|
isAltActive
|
||||||
|
? "text-black dark:text-white"
|
||||||
|
: "text-slate-800/20 dark:text-slate-300/20",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
Alt
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={cx(
|
||||||
|
"shrink-0 p-1 px-1.5 text-xs",
|
||||||
|
isMetaActive
|
||||||
|
? "text-black dark:text-white"
|
||||||
|
: "text-slate-800/20 dark:text-slate-300/20",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
Meta
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={cx(
|
||||||
|
"shrink-0 p-1 px-1.5 text-xs",
|
||||||
|
isAltGrActive
|
||||||
|
? "text-black dark:text-white"
|
||||||
|
: "text-slate-800/20 dark:text-slate-300/20",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
AltGr
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
className={cx(
|
className={cx(
|
||||||
"shrink-0 p-1 px-1.5 text-xs",
|
"shrink-0 p-1 px-1.5 text-xs",
|
||||||
|
|
|
@ -27,6 +27,7 @@ const AttachIcon = ({ className }: { className?: string }) => {
|
||||||
|
|
||||||
function KeyboardWrapper() {
|
function KeyboardWrapper() {
|
||||||
const [layoutName, setLayoutName] = useState("default");
|
const [layoutName, setLayoutName] = useState("default");
|
||||||
|
const [depressedButtons, setDepressedButtons] = useState("");
|
||||||
|
|
||||||
const keyboardRef = useRef<HTMLDivElement>(null);
|
const keyboardRef = useRef<HTMLDivElement>(null);
|
||||||
const showAttachedVirtualKeyboard = useUiStore(
|
const showAttachedVirtualKeyboard = useUiStore(
|
||||||
|
@ -54,6 +55,21 @@ function KeyboardWrapper() {
|
||||||
|
|
||||||
const setIsCapsLockActive = useHidStore(state => state.setIsCapsLockActive);
|
const setIsCapsLockActive = useHidStore(state => state.setIsCapsLockActive);
|
||||||
|
|
||||||
|
const isShiftActive = useHidStore(state => state.isShiftActive);
|
||||||
|
const setIsShiftActive = useHidStore(state => state.setIsShiftActive);
|
||||||
|
|
||||||
|
const isCtrlActive = useHidStore(state => state.isCtrlActive);
|
||||||
|
const setIsCtrlActive = useHidStore(state => state.setIsCtrlActive);
|
||||||
|
|
||||||
|
const isAltActive = useHidStore(state => state.isAltActive);
|
||||||
|
const setIsAltActive = useHidStore(state => state.setIsAltActive);
|
||||||
|
|
||||||
|
const isMetaActive = useHidStore(state => state.isMetaActive);
|
||||||
|
const setIsMetaActive = useHidStore(state => state.setIsMetaActive);
|
||||||
|
|
||||||
|
const isAltGrActive = useHidStore(state => state.isAltGrActive);
|
||||||
|
const setIsAltGrActive = useHidStore(state => state.setIsAltGrActive);
|
||||||
|
|
||||||
const startDrag = useCallback((e: MouseEvent | TouchEvent) => {
|
const startDrag = useCallback((e: MouseEvent | TouchEvent) => {
|
||||||
if (!keyboardRef.current) return;
|
if (!keyboardRef.current) return;
|
||||||
if (e instanceof TouchEvent && e.touches.length > 1) return;
|
if (e instanceof TouchEvent && e.touches.length > 1) return;
|
||||||
|
@ -123,80 +139,123 @@ function KeyboardWrapper() {
|
||||||
};
|
};
|
||||||
}, [endDrag, onDrag, startDrag]);
|
}, [endDrag, onDrag, startDrag]);
|
||||||
|
|
||||||
const onKeyDown = useCallback(
|
useEffect(() => {
|
||||||
(key: string) => {
|
// if you have the CapsLock "down", then the shift state is inverted
|
||||||
const isKeyShift = key === "{shift}" || key === "ShiftLeft" || key === "ShiftRight";
|
const effectiveShift = isCapsLockActive ? false === isShiftActive : isShiftActive;
|
||||||
const isKeyCaps = key === "CapsLock";
|
setLayoutName(effectiveShift ? "shift" : "default");
|
||||||
const cleanKey = key.replace(/[()]/g, "");
|
},
|
||||||
const keyHasShiftModifier = key.includes("(");
|
[setLayoutName, isCapsLockActive, isShiftActive]
|
||||||
|
);
|
||||||
|
|
||||||
// Handle toggle of layout for shift or caps lock
|
// this causes the buttons to look depressed/clicked depending on the sticky state
|
||||||
const toggleLayout = () => {
|
useEffect(() => {
|
||||||
setLayoutName(prevLayout => (prevLayout === "default" ? "shift" : "default"));
|
let buttons = "None "; // make sure we name at least one (fake) button
|
||||||
};
|
if (isCapsLockActive) buttons += "CapsLock ";
|
||||||
|
if (isShiftActive) buttons += "ShiftLeft ShiftRight ";
|
||||||
|
if (isCtrlActive) buttons += "ControlLeft ControlRight ";
|
||||||
|
if (isAltActive) buttons += "AltLeft AltRight ";
|
||||||
|
if (isMetaActive) buttons += "MetaLeft MetaRight ";
|
||||||
|
setDepressedButtons(buttons.trimEnd());
|
||||||
|
},
|
||||||
|
[setDepressedButtons, isCapsLockActive, isShiftActive, isCtrlActive, isAltActive, isMetaActive, isAltGrActive]
|
||||||
|
);
|
||||||
|
|
||||||
if (key === "CtrlAltDelete") {
|
const onKeyPress = useCallback((key: string) => {
|
||||||
sendKeyboardEvent(
|
// handle the fake combo keys first
|
||||||
[keys["Delete"]],
|
if (key === "CtrlAltDelete") {
|
||||||
[modifiers["ControlLeft"], modifiers["AltLeft"]],
|
sendKeyboardEvent(
|
||||||
);
|
[keys["Delete"]],
|
||||||
setTimeout(resetKeyboardState, 100);
|
[modifiers["ControlLeft"], modifiers["AltLeft"]],
|
||||||
return;
|
);
|
||||||
}
|
setTimeout(resetKeyboardState, 100);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (key === "AltMetaEscape") {
|
if (key === "AltMetaEscape") {
|
||||||
sendKeyboardEvent(
|
sendKeyboardEvent(
|
||||||
[keys["Escape"]],
|
[keys["Escape"]],
|
||||||
[modifiers["MetaLeft"], modifiers["AltLeft"]],
|
[modifiers["MetaLeft"], modifiers["AltLeft"]],
|
||||||
);
|
);
|
||||||
|
|
||||||
setTimeout(resetKeyboardState, 100);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (key === "CtrlAltBackspace") {
|
|
||||||
sendKeyboardEvent(
|
|
||||||
[keys["Backspace"]],
|
|
||||||
[modifiers["ControlLeft"], modifiers["AltLeft"]],
|
|
||||||
);
|
|
||||||
|
|
||||||
setTimeout(resetKeyboardState, 100);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isKeyShift || isKeyCaps) {
|
|
||||||
toggleLayout();
|
|
||||||
|
|
||||||
if (isCapsLockActive) {
|
|
||||||
if (!isKeyboardLedManagedByHost) {
|
|
||||||
setIsCapsLockActive(false);
|
|
||||||
}
|
|
||||||
sendKeyboardEvent([keys["CapsLock"]], []);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle caps lock state change
|
|
||||||
if (isKeyCaps && !isKeyboardLedManagedByHost) {
|
|
||||||
setIsCapsLockActive(!isCapsLockActive);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Collect new active keys and modifiers
|
|
||||||
const newKeys = keys[cleanKey] ? [keys[cleanKey]] : [];
|
|
||||||
const newModifiers =
|
|
||||||
keyHasShiftModifier && !isCapsLockActive ? [modifiers["ShiftLeft"]] : [];
|
|
||||||
|
|
||||||
// Update current keys and modifiers
|
|
||||||
sendKeyboardEvent(newKeys, newModifiers);
|
|
||||||
|
|
||||||
// If shift was used as a modifier and caps lock is not active, revert to default layout
|
|
||||||
if (keyHasShiftModifier && !isCapsLockActive) {
|
|
||||||
setLayoutName("default");
|
|
||||||
}
|
|
||||||
|
|
||||||
setTimeout(resetKeyboardState, 100);
|
setTimeout(resetKeyboardState, 100);
|
||||||
},
|
return;
|
||||||
[isCapsLockActive, isKeyboardLedManagedByHost, sendKeyboardEvent, resetKeyboardState, setIsCapsLockActive],
|
}
|
||||||
|
|
||||||
|
if (key === "CtrlAltBackspace") {
|
||||||
|
sendKeyboardEvent(
|
||||||
|
[keys["Backspace"]],
|
||||||
|
[modifiers["ControlLeft"], modifiers["AltLeft"]],
|
||||||
|
);
|
||||||
|
|
||||||
|
setTimeout(resetKeyboardState, 100);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// strip away the parens for shifted characters
|
||||||
|
const cleanKey = key.replace(/[()]/g, "");
|
||||||
|
|
||||||
|
const passthrough = ["PrintScreen", "SystemRequest", "Pause", "Break", "ScrollLock", "Enter", "Space"].find((value) => value === cleanKey);
|
||||||
|
|
||||||
|
if (passthrough) {
|
||||||
|
emitkeycode(cleanKey);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// adjust the sticky state of the Shift/Ctrl/Alt/Meta/AltGr
|
||||||
|
if (key === "CapsLock" && !isKeyboardLedManagedByHost)
|
||||||
|
setIsCapsLockActive(!isCapsLockActive);
|
||||||
|
else if (key === "ShiftLeft" || key === "ShiftRight")
|
||||||
|
setIsShiftActive(!isShiftActive);
|
||||||
|
else if (key === "ControlLeft" || key === "ControlRight")
|
||||||
|
setIsCtrlActive(!isCtrlActive);
|
||||||
|
else if (key === "AltLeft" || key === "AltRight")
|
||||||
|
setIsAltActive(!isAltActive);
|
||||||
|
else if (key === "MetaLeft" || key === "MetaRight")
|
||||||
|
setIsMetaActive(!isMetaActive);
|
||||||
|
else if (key === "AltGr")
|
||||||
|
setIsAltGrActive(!isAltGrActive);
|
||||||
|
|
||||||
|
emitkeycode(cleanKey);
|
||||||
|
|
||||||
|
function emitkeycode(key: string) {
|
||||||
|
const effectiveMods: number[] = [];
|
||||||
|
|
||||||
|
if (isShiftActive)
|
||||||
|
effectiveMods.push(modifiers["ShiftLeft"]);
|
||||||
|
|
||||||
|
if (isCtrlActive)
|
||||||
|
effectiveMods.push(modifiers["ControlLeft"]);
|
||||||
|
|
||||||
|
if (isAltActive)
|
||||||
|
effectiveMods.push(modifiers["AltLeft"]);
|
||||||
|
|
||||||
|
if (isMetaActive)
|
||||||
|
effectiveMods.push(modifiers["MetaLeft"]);
|
||||||
|
|
||||||
|
if (isAltGrActive) {
|
||||||
|
effectiveMods.push(modifiers["MetaRight"]);
|
||||||
|
effectiveMods.push(modifiers["CtrlLeft"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const keycode = keys[key];
|
||||||
|
if (keycode) {
|
||||||
|
// send the keycode with modifiers
|
||||||
|
sendKeyboardEvent([keycode], effectiveMods);
|
||||||
|
}
|
||||||
|
|
||||||
|
// release the key (if one pressed), but retain the modifiers
|
||||||
|
setTimeout(() => sendKeyboardEvent([], effectiveMods), 50);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[isKeyboardLedManagedByHost,
|
||||||
|
setIsCapsLockActive, isCapsLockActive,
|
||||||
|
setIsShiftActive, isShiftActive,
|
||||||
|
setIsCtrlActive, isCtrlActive,
|
||||||
|
setIsAltActive, isAltActive,
|
||||||
|
setIsMetaActive, isMetaActive,
|
||||||
|
setIsAltGrActive, isAltGrActive,
|
||||||
|
sendKeyboardEvent, resetKeyboardState
|
||||||
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
const virtualKeyboard = useHidStore(state => state.isVirtualKeyboardEnabled);
|
const virtualKeyboard = useHidStore(state => state.isVirtualKeyboardEnabled);
|
||||||
|
@ -276,12 +335,16 @@ function KeyboardWrapper() {
|
||||||
<Keyboard
|
<Keyboard
|
||||||
baseClass="simple-keyboard-main"
|
baseClass="simple-keyboard-main"
|
||||||
layoutName={layoutName}
|
layoutName={layoutName}
|
||||||
onKeyPress={onKeyDown}
|
onKeyPress={onKeyPress}
|
||||||
buttonTheme={[
|
buttonTheme={[
|
||||||
{
|
{
|
||||||
class: "combination-key",
|
class: "combination-key",
|
||||||
buttons: "CtrlAltDelete AltMetaEscape CtrlAltBackspace",
|
buttons: "CtrlAltDelete AltMetaEscape CtrlAltBackspace",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
class: "depressed-key",
|
||||||
|
buttons: depressedButtons
|
||||||
|
},
|
||||||
]}
|
]}
|
||||||
display={keyDisplayMap}
|
display={keyDisplayMap}
|
||||||
layout={{
|
layout={{
|
||||||
|
@ -305,8 +368,6 @@ function KeyboardWrapper() {
|
||||||
],
|
],
|
||||||
}}
|
}}
|
||||||
disableButtonHold={true}
|
disableButtonHold={true}
|
||||||
syncInstanceInputs={true}
|
|
||||||
debug={false}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="controlArrows">
|
<div className="controlArrows">
|
||||||
|
@ -314,25 +375,24 @@ function KeyboardWrapper() {
|
||||||
baseClass="simple-keyboard-control"
|
baseClass="simple-keyboard-control"
|
||||||
theme="simple-keyboard hg-theme-default hg-layout-default"
|
theme="simple-keyboard hg-theme-default hg-layout-default"
|
||||||
layoutName={layoutName}
|
layoutName={layoutName}
|
||||||
onKeyPress={onKeyDown}
|
onKeyPress={onKeyPress}
|
||||||
display={keyDisplayMap}
|
display={keyDisplayMap}
|
||||||
layout={{
|
layout={{
|
||||||
default: ["PrintScreen ScrollLock Pause", "Insert Home Pageup", "Delete End Pagedown"],
|
default: ["PrintScreen ScrollLock Pause", "Insert Home PageUp", "Delete End PageDown"],
|
||||||
shift: ["(PrintScreen) ScrollLock (Pause)", "Insert Home Pageup", "Delete End Pagedown"],
|
shift: ["(PrintScreen) ScrollLock (Pause)", "Insert Home PageUp", "Delete End PageDown"],
|
||||||
}}
|
}}
|
||||||
syncInstanceInputs={true}
|
disableButtonHold={true}
|
||||||
debug={false}
|
|
||||||
/>
|
/>
|
||||||
<Keyboard
|
<Keyboard
|
||||||
baseClass="simple-keyboard-arrows"
|
baseClass="simple-keyboard-arrows"
|
||||||
theme="simple-keyboard hg-theme-default hg-layout-default"
|
theme="simple-keyboard hg-theme-default hg-layout-default"
|
||||||
onKeyPress={onKeyDown}
|
onKeyPress={onKeyPress}
|
||||||
display={keyDisplayMap}
|
display={keyDisplayMap}
|
||||||
layout={{
|
layout={{
|
||||||
default: ["ArrowUp", "ArrowLeft ArrowDown ArrowRight"],
|
default: ["ArrowUp", "ArrowLeft ArrowDown ArrowRight"],
|
||||||
|
shift: ["ArrowUp", "ArrowLeft ArrowDown ArrowRight"],
|
||||||
}}
|
}}
|
||||||
syncInstanceInputs={true}
|
disableButtonHold={true}
|
||||||
debug={false}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -71,6 +71,15 @@ export default function WebRTCVideo() {
|
||||||
const hdmiError = ["no_lock", "no_signal", "out_of_range"].includes(hdmiState);
|
const hdmiError = ["no_lock", "no_signal", "out_of_range"].includes(hdmiState);
|
||||||
const isVideoLoading = !isPlaying;
|
const isVideoLoading = !isPlaying;
|
||||||
|
|
||||||
|
// Keyboard related states
|
||||||
|
const {
|
||||||
|
setIsShiftActive,
|
||||||
|
setIsCtrlActive,
|
||||||
|
setIsAltActive,
|
||||||
|
setIsMetaActive,
|
||||||
|
setIsAltGrActive
|
||||||
|
} = useHidStore();
|
||||||
|
|
||||||
const [blockWheelEvent, setBlockWheelEvent] = useState(false);
|
const [blockWheelEvent, setBlockWheelEvent] = useState(false);
|
||||||
|
|
||||||
// Misc states and hooks
|
// Misc states and hooks
|
||||||
|
@ -423,6 +432,12 @@ export default function WebRTCVideo() {
|
||||||
setIsScrollLockActive(e.getModifierState("ScrollLock"));
|
setIsScrollLockActive(e.getModifierState("ScrollLock"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setIsShiftActive(e.getModifierState("Shift"))
|
||||||
|
setIsCtrlActive(e.getModifierState("Control"))
|
||||||
|
setIsAltActive(e.getModifierState("Alt"))
|
||||||
|
setIsMetaActive(e.getModifierState("Meta"))
|
||||||
|
setIsAltGrActive(e.getModifierState("AltGraph"))
|
||||||
|
|
||||||
if (code == "IntlBackslash" && ["`", "~"].includes(key)) {
|
if (code == "IntlBackslash" && ["`", "~"].includes(key)) {
|
||||||
code = "Backquote";
|
code = "Backquote";
|
||||||
} else if (code == "Backquote" && ["§", "±"].includes(key)) {
|
} else if (code == "Backquote" && ["§", "±"].includes(key)) {
|
||||||
|
@ -452,12 +467,17 @@ export default function WebRTCVideo() {
|
||||||
sendKeyboardEvent([...new Set(newKeys)], [...new Set(newModifiers)]);
|
sendKeyboardEvent([...new Set(newKeys)], [...new Set(newModifiers)]);
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
handleModifierKeys,
|
|
||||||
sendKeyboardEvent,
|
|
||||||
isKeyboardLedManagedByHost,
|
isKeyboardLedManagedByHost,
|
||||||
setIsNumLockActive,
|
setIsNumLockActive,
|
||||||
setIsCapsLockActive,
|
setIsCapsLockActive,
|
||||||
setIsScrollLockActive,
|
setIsScrollLockActive,
|
||||||
|
setIsShiftActive,
|
||||||
|
setIsCtrlActive,
|
||||||
|
setIsAltActive,
|
||||||
|
setIsMetaActive,
|
||||||
|
setIsAltGrActive,
|
||||||
|
handleModifierKeys,
|
||||||
|
sendKeyboardEvent
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -472,6 +492,12 @@ export default function WebRTCVideo() {
|
||||||
setIsScrollLockActive(e.getModifierState("ScrollLock"));
|
setIsScrollLockActive(e.getModifierState("ScrollLock"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setIsShiftActive(e.getModifierState("Shift"))
|
||||||
|
setIsCtrlActive(e.getModifierState("Control"))
|
||||||
|
setIsAltActive(e.getModifierState("Alt"))
|
||||||
|
setIsMetaActive(e.getModifierState("Meta"))
|
||||||
|
setIsAltGrActive(e.getModifierState("AltGraph"))
|
||||||
|
|
||||||
// Filtering out the key that was just released (keys[e.code])
|
// Filtering out the key that was just released (keys[e.code])
|
||||||
const newKeys = prev.activeKeys.filter(k => k !== keys[e.code]).filter(Boolean);
|
const newKeys = prev.activeKeys.filter(k => k !== keys[e.code]).filter(Boolean);
|
||||||
|
|
||||||
|
@ -484,12 +510,17 @@ export default function WebRTCVideo() {
|
||||||
sendKeyboardEvent([...new Set(newKeys)], [...new Set(newModifiers)]);
|
sendKeyboardEvent([...new Set(newKeys)], [...new Set(newModifiers)]);
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
handleModifierKeys,
|
|
||||||
sendKeyboardEvent,
|
|
||||||
isKeyboardLedManagedByHost,
|
isKeyboardLedManagedByHost,
|
||||||
setIsNumLockActive,
|
setIsNumLockActive,
|
||||||
setIsCapsLockActive,
|
setIsCapsLockActive,
|
||||||
setIsScrollLockActive,
|
setIsScrollLockActive,
|
||||||
|
setIsShiftActive,
|
||||||
|
setIsCtrlActive,
|
||||||
|
setIsAltActive,
|
||||||
|
setIsMetaActive,
|
||||||
|
setIsAltGrActive,
|
||||||
|
handleModifierKeys,
|
||||||
|
sendKeyboardEvent
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -8,12 +8,14 @@ import { useJsonRpc } from "@/hooks/useJsonRpc";
|
||||||
import notifications from "@/notifications";
|
import notifications from "@/notifications";
|
||||||
import FieldLabel from "@components/FieldLabel";
|
import FieldLabel from "@components/FieldLabel";
|
||||||
import LoadingSpinner from "@components/LoadingSpinner";
|
import LoadingSpinner from "@components/LoadingSpinner";
|
||||||
|
import {SelectMenuBasic} from "@components/SelectMenuBasic";
|
||||||
|
|
||||||
interface DCPowerState {
|
interface DCPowerState {
|
||||||
isOn: boolean;
|
isOn: boolean;
|
||||||
voltage: number;
|
voltage: number;
|
||||||
current: number;
|
current: number;
|
||||||
power: number;
|
power: number;
|
||||||
|
restoreState: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function DCPowerControl() {
|
export function DCPowerControl() {
|
||||||
|
@ -43,6 +45,20 @@ export function DCPowerControl() {
|
||||||
getDCPowerState(); // Refresh state after change
|
getDCPowerState(); // Refresh state after change
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
const handleRestoreChange = (state: number) => {
|
||||||
|
// const state = powerState?.restoreState === 0 ? 1 : powerState?.restoreState === 1 ? 2 : 0;
|
||||||
|
send("setDCRestoreState", { state }, resp => {
|
||||||
|
if ("error" in resp) {
|
||||||
|
notifications.error(
|
||||||
|
`Failed to set DC power state: ${resp.error.data || "Unknown error"}`,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
getDCPowerState(); // Refresh state after change
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getDCPowerState();
|
getDCPowerState();
|
||||||
|
@ -63,7 +79,7 @@ export function DCPowerControl() {
|
||||||
<LoadingSpinner className="h-6 w-6 text-blue-500 dark:text-blue-400" />
|
<LoadingSpinner className="h-6 w-6 text-blue-500 dark:text-blue-400" />
|
||||||
</Card>
|
</Card>
|
||||||
) : (
|
) : (
|
||||||
<Card className="h-[160px] animate-fadeIn opacity-0">
|
<Card className="animate-fadeIn opacity-0">
|
||||||
<div className="space-y-4 p-3">
|
<div className="space-y-4 p-3">
|
||||||
{/* Power Controls */}
|
{/* Power Controls */}
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
|
@ -84,6 +100,21 @@ export function DCPowerControl() {
|
||||||
onClick={() => handlePowerToggle(false)}
|
onClick={() => handlePowerToggle(false)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
{powerState.restoreState > -1 ? (
|
||||||
|
<div className="flex items-center">
|
||||||
|
<SelectMenuBasic
|
||||||
|
size="SM"
|
||||||
|
label="Restore Power Loss"
|
||||||
|
value={powerState.restoreState}
|
||||||
|
onChange={e => handleRestoreChange(parseInt(e.target.value))}
|
||||||
|
options={[
|
||||||
|
{ value: '0', label: "Power OFF" },
|
||||||
|
{ value: '1', label: "Power ON" },
|
||||||
|
{ value: '2', label: "Last State" },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
<hr className="border-slate-700/30 dark:border-slate-600/30" />
|
<hr className="border-slate-700/30 dark:border-slate-600/30" />
|
||||||
|
|
||||||
{/* Status Display */}
|
{/* Status Display */}
|
||||||
|
|
|
@ -308,9 +308,6 @@ interface SettingsState {
|
||||||
keyboardLayout: string;
|
keyboardLayout: string;
|
||||||
setKeyboardLayout: (layout: string) => void;
|
setKeyboardLayout: (layout: string) => void;
|
||||||
|
|
||||||
actionBarCtrlAltDel: boolean;
|
|
||||||
setActionBarCtrlAltDel: (enabled: boolean) => void;
|
|
||||||
|
|
||||||
keyboardLedSync: KeyboardLedSync;
|
keyboardLedSync: KeyboardLedSync;
|
||||||
setKeyboardLedSync: (sync: KeyboardLedSync) => void;
|
setKeyboardLedSync: (sync: KeyboardLedSync) => void;
|
||||||
|
|
||||||
|
@ -359,9 +356,6 @@ export const useSettingsStore = create(
|
||||||
keyboardLayout: "en-US",
|
keyboardLayout: "en-US",
|
||||||
setKeyboardLayout: layout => set({ keyboardLayout: layout }),
|
setKeyboardLayout: layout => set({ keyboardLayout: layout }),
|
||||||
|
|
||||||
actionBarCtrlAltDel: false,
|
|
||||||
setActionBarCtrlAltDel: enabled => set({ actionBarCtrlAltDel: enabled }),
|
|
||||||
|
|
||||||
keyboardLedSync: "auto",
|
keyboardLedSync: "auto",
|
||||||
setKeyboardLedSync: sync => set({ keyboardLedSync: sync }),
|
setKeyboardLedSync: sync => set({ keyboardLedSync: sync }),
|
||||||
|
|
||||||
|
@ -481,6 +475,21 @@ export interface HidState {
|
||||||
isVirtualKeyboardEnabled: boolean;
|
isVirtualKeyboardEnabled: boolean;
|
||||||
setVirtualKeyboardEnabled: (enabled: boolean) => void;
|
setVirtualKeyboardEnabled: (enabled: boolean) => void;
|
||||||
|
|
||||||
|
isShiftActive: boolean;
|
||||||
|
setIsShiftActive: (enabled: boolean) => void;
|
||||||
|
|
||||||
|
isCtrlActive: boolean;
|
||||||
|
setIsCtrlActive: (enabled: boolean) => void;
|
||||||
|
|
||||||
|
isAltActive: boolean;
|
||||||
|
setIsAltActive: (enabled: boolean) => void;
|
||||||
|
|
||||||
|
isMetaActive: boolean;
|
||||||
|
setIsMetaActive: (enabled: boolean) => void;
|
||||||
|
|
||||||
|
isAltGrActive: boolean;
|
||||||
|
setIsAltGrActive: (enabled: boolean) => void;
|
||||||
|
|
||||||
isPasteModeEnabled: boolean;
|
isPasteModeEnabled: boolean;
|
||||||
setPasteModeEnabled: (enabled: boolean) => void;
|
setPasteModeEnabled: (enabled: boolean) => void;
|
||||||
|
|
||||||
|
@ -527,6 +536,21 @@ export const useHidStore = create<HidState>((set, get) => ({
|
||||||
isVirtualKeyboardEnabled: false,
|
isVirtualKeyboardEnabled: false,
|
||||||
setVirtualKeyboardEnabled: enabled => set({ isVirtualKeyboardEnabled: enabled }),
|
setVirtualKeyboardEnabled: enabled => set({ isVirtualKeyboardEnabled: enabled }),
|
||||||
|
|
||||||
|
isShiftActive: false,
|
||||||
|
setIsShiftActive: enabled => set({ isShiftActive: enabled }),
|
||||||
|
|
||||||
|
isCtrlActive: false,
|
||||||
|
setIsCtrlActive: enabled => set({ isCtrlActive: enabled }),
|
||||||
|
|
||||||
|
isAltActive: false,
|
||||||
|
setIsAltActive: enabled => set({ isAltActive: enabled }),
|
||||||
|
|
||||||
|
isMetaActive: false,
|
||||||
|
setIsMetaActive: enabled => set({ isMetaActive: enabled }),
|
||||||
|
|
||||||
|
isAltGrActive: false,
|
||||||
|
setIsAltGrActive: enabled => set({ isAltGrActive: enabled }),
|
||||||
|
|
||||||
isPasteModeEnabled: false,
|
isPasteModeEnabled: false,
|
||||||
setPasteModeEnabled: enabled => set({ isPasteModeEnabled: enabled }),
|
setPasteModeEnabled: enabled => set({ isPasteModeEnabled: enabled }),
|
||||||
|
|
||||||
|
|
|
@ -222,6 +222,10 @@ video::-webkit-media-controls {
|
||||||
background: none;
|
background: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.simple-keyboard .hg-button.depressed-key {
|
||||||
|
@apply border-gray-800! border-b-gray-600! border-t-gray-900! bg-gray-700!;
|
||||||
|
}
|
||||||
|
|
||||||
.simple-keyboard .hg-button.selectedButton {
|
.simple-keyboard .hg-button.selectedButton {
|
||||||
background: rgba(5, 25, 70, 0.53);
|
background: rgba(5, 25, 70, 0.53);
|
||||||
@apply text-white;
|
@apply text-white;
|
||||||
|
|
|
@ -5,14 +5,14 @@ export const keys = {
|
||||||
ArrowLeft: 0x50,
|
ArrowLeft: 0x50,
|
||||||
ArrowRight: 0x4f,
|
ArrowRight: 0x4f,
|
||||||
ArrowUp: 0x52,
|
ArrowUp: 0x52,
|
||||||
Backquote: 0x35,
|
Backquote: 0x35, // aka Grave
|
||||||
Backslash: 0x31,
|
Backslash: 0x31,
|
||||||
Backspace: 0x2a,
|
Backspace: 0x2a,
|
||||||
BracketLeft: 0x2f,
|
BracketLeft: 0x2f, // aka LeftBrace
|
||||||
BracketRight: 0x30,
|
BracketRight: 0x30, // aka RightBrace
|
||||||
CapsLock: 0x39,
|
CapsLock: 0x39,
|
||||||
Comma: 0x36,
|
Comma: 0x36,
|
||||||
ContextMenu: 0,
|
Compose: 0x65,
|
||||||
Delete: 0x4c,
|
Delete: 0x4c,
|
||||||
Digit0: 0x27,
|
Digit0: 0x27,
|
||||||
Digit1: 0x1e,
|
Digit1: 0x1e,
|
||||||
|
@ -41,6 +41,18 @@ export const keys = {
|
||||||
F11: 0x44,
|
F11: 0x44,
|
||||||
F12: 0x45,
|
F12: 0x45,
|
||||||
F13: 0x68,
|
F13: 0x68,
|
||||||
|
F14: 0x69,
|
||||||
|
F15: 0x6a,
|
||||||
|
F16: 0x6b,
|
||||||
|
F17: 0x6c,
|
||||||
|
F18: 0x6d,
|
||||||
|
F19: 0x6e,
|
||||||
|
F20: 0x6f,
|
||||||
|
F21: 0x70,
|
||||||
|
F22: 0x71,
|
||||||
|
F23: 0x72,
|
||||||
|
F24: 0x73,
|
||||||
|
HashTilde: 0x32,
|
||||||
Home: 0x4a,
|
Home: 0x4a,
|
||||||
Insert: 0x49,
|
Insert: 0x49,
|
||||||
IntlBackslash: 0x64,
|
IntlBackslash: 0x64,
|
||||||
|
@ -70,37 +82,58 @@ export const keys = {
|
||||||
KeyX: 0x1b,
|
KeyX: 0x1b,
|
||||||
KeyY: 0x1c,
|
KeyY: 0x1c,
|
||||||
KeyZ: 0x1d,
|
KeyZ: 0x1d,
|
||||||
|
KeypadComma: 0x85,
|
||||||
|
KeypadEqual: 0x86,
|
||||||
|
KeyRO: 0x87,
|
||||||
|
KatakanaHiragana: 0x88,
|
||||||
|
Yen: 0x89,
|
||||||
|
Henkan: 0x8a,
|
||||||
|
Muhenkan: 0x8b,
|
||||||
|
KPJPComma: 0x8c,
|
||||||
|
International7: 0x8d,
|
||||||
|
International8: 0x8e,
|
||||||
|
International9: 0x8f,
|
||||||
|
Hangeul: 0x90,
|
||||||
|
Hanja: 0x91,
|
||||||
|
Katakana: 0x92,
|
||||||
|
Hiragana: 0x93,
|
||||||
|
Zenkakuhankaku:0x94,
|
||||||
|
KeypadLeftParen: 0xb6,
|
||||||
|
KeypadRightParen: 0xb7,
|
||||||
KeypadExclamation: 0xcf,
|
KeypadExclamation: 0xcf,
|
||||||
Minus: 0x2d,
|
Minus: 0x2d,
|
||||||
NumLock: 0x53,
|
None: 0x00,
|
||||||
Numpad0: 0x62,
|
NumLock: 0x53, // and Clear
|
||||||
Numpad1: 0x59,
|
Numpad0: 0x62, // and Insert
|
||||||
Numpad2: 0x5a,
|
Numpad1: 0x59, // and End
|
||||||
Numpad3: 0x5b,
|
Numpad2: 0x5a, // and Down Arrow
|
||||||
Numpad4: 0x5c,
|
Numpad3: 0x5b, // and Page Down
|
||||||
|
Numpad4: 0x5c, // and Left Arrow
|
||||||
Numpad5: 0x5d,
|
Numpad5: 0x5d,
|
||||||
Numpad6: 0x5e,
|
Numpad6: 0x5e, // and Right Arrow
|
||||||
Numpad7: 0x5f,
|
Numpad7: 0x5f, // and Home
|
||||||
Numpad8: 0x60,
|
Numpad8: 0x60, // and Up Arrow
|
||||||
Numpad9: 0x61,
|
Numpad9: 0x61, // and Page Up
|
||||||
NumpadAdd: 0x57,
|
NumpadPlus: 0x57,
|
||||||
NumpadDivide: 0x54,
|
NumpadSlash: 0x54,
|
||||||
NumpadEnter: 0x58,
|
NumpadEnter: 0x58,
|
||||||
NumpadEqual: 0x67,
|
NumpadEqual: 0x67,
|
||||||
NumpadMultiply: 0x55,
|
NumpadAsterisk: 0x55,
|
||||||
NumpadSubtract: 0x56,
|
NumpadMinus: 0x56,
|
||||||
NumpadDecimal: 0x63,
|
NumpadDecimal: 0x63, // aka NumpadDot and Delete
|
||||||
|
Overflow: 0x01,
|
||||||
PageDown: 0x4e,
|
PageDown: 0x4e,
|
||||||
PageUp: 0x4b,
|
PageUp: 0x4b,
|
||||||
Period: 0x37,
|
Period: 0x37, // aka Dot
|
||||||
PrintScreen: 0x46,
|
PrintScreen: 0x46,
|
||||||
Pause: 0x48,
|
Pause: 0x48,
|
||||||
Quote: 0x34,
|
Power: 0x66,
|
||||||
|
Quote: 0x34, // aka Apostrophe
|
||||||
ScrollLock: 0x47,
|
ScrollLock: 0x47,
|
||||||
Semicolon: 0x33,
|
Semicolon: 0x33,
|
||||||
Slash: 0x38,
|
Slash: 0x38,
|
||||||
Space: 0x2c,
|
Space: 0x2c,
|
||||||
SystemRequest: 0x9a,
|
SystemRequest: 0x9a, // aka Attention
|
||||||
Tab: 0x2b,
|
Tab: 0x2b,
|
||||||
} as Record<string, number>;
|
} as Record<string, number>;
|
||||||
|
|
||||||
|
@ -131,23 +164,23 @@ export const keyDisplayMap: Record<string, string> = {
|
||||||
AltMetaEscape: "Alt + Meta + Escape",
|
AltMetaEscape: "Alt + Meta + Escape",
|
||||||
CtrlAltBackspace: "Ctrl + Alt + Backspace",
|
CtrlAltBackspace: "Ctrl + Alt + Backspace",
|
||||||
Escape: "esc",
|
Escape: "esc",
|
||||||
Tab: "tab",
|
Tab: "tab ⇥",
|
||||||
Backspace: "backspace",
|
Backspace: "backspace ⌫",
|
||||||
"(Backspace)": "backspace",
|
|
||||||
Enter: "enter",
|
Enter: "enter",
|
||||||
CapsLock: "caps lock",
|
CapsLock: "caps lock ⇪",
|
||||||
ShiftLeft: "shift",
|
ShiftLeft: "shift ⇧",
|
||||||
ShiftRight: "shift",
|
ShiftRight: "⇧ shift",
|
||||||
ControlLeft: "ctrl",
|
ControlLeft: "ctrl ⌃",
|
||||||
AltLeft: "alt",
|
ControlRight: "⌃ ctrl",
|
||||||
AltRight: "alt",
|
AltLeft: "alt ⌥",
|
||||||
MetaLeft: "meta",
|
AltRight: "⌥ alt",
|
||||||
MetaRight: "meta",
|
MetaLeft: "meta ⌘", // "meta ⊞" for windows
|
||||||
|
MetaRight: "⌘ meta",// "≣ meta" for windows
|
||||||
Space: " ",
|
Space: " ",
|
||||||
Insert: "insert",
|
Insert: "ins",
|
||||||
Home: "home",
|
Home: "home",
|
||||||
PageUp: "page up",
|
PageUp: "page up",
|
||||||
Delete: "delete",
|
Delete: "del ⌦",
|
||||||
End: "end",
|
End: "end",
|
||||||
PageDown: "page down",
|
PageDown: "page down",
|
||||||
ArrowLeft: "←",
|
ArrowLeft: "←",
|
||||||
|
@ -213,13 +246,12 @@ export const keyDisplayMap: Record<string, string> = {
|
||||||
Numpad0: "Num 0", Numpad1: "Num 1", Numpad2: "Num 2",
|
Numpad0: "Num 0", Numpad1: "Num 1", Numpad2: "Num 2",
|
||||||
Numpad3: "Num 3", Numpad4: "Num 4", Numpad5: "Num 5",
|
Numpad3: "Num 3", Numpad4: "Num 4", Numpad5: "Num 5",
|
||||||
Numpad6: "Num 6", Numpad7: "Num 7", Numpad8: "Num 8",
|
Numpad6: "Num 6", Numpad7: "Num 7", Numpad8: "Num 8",
|
||||||
Numpad9: "Num 9", NumpadAdd: "Num +", NumpadSubtract: "Num -",
|
Numpad9: "Num 9", NumpadPlus: "Num +", NumpadMinus: "Num -",
|
||||||
NumpadMultiply: "Num *", NumpadDivide: "Num /", NumpadDecimal: "Num .",
|
NumpadAsterisk: "Num *", NumpadSlash: "Num /", NumpadDecimal: "Num .",
|
||||||
NumpadEqual: "Num =", NumpadEnter: "Num Enter",
|
NumpadEqual: "Num =", NumpadEnter: "Num Enter",
|
||||||
NumLock: "Num Lock",
|
NumLock: "Num Lock",
|
||||||
|
|
||||||
// Modals
|
// Modals
|
||||||
PrintScreen: "prt sc", ScrollLock: "scr lk", Pause: "pause",
|
PrintScreen: "prt sc", ScrollLock: "scr lk", Pause: "pause",
|
||||||
"(PrintScreen)": "sys rq", "(Pause)": "break",
|
"(PrintScreen)": "sys rq", "(Pause)": "break"
|
||||||
SystemRequest: "sys rq", Break: "break"
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -42,6 +42,7 @@ import SettingsHardwareRoute from "./routes/devices.$id.settings.hardware";
|
||||||
import SettingsVideoRoute from "./routes/devices.$id.settings.video";
|
import SettingsVideoRoute from "./routes/devices.$id.settings.video";
|
||||||
import SettingsAppearanceRoute from "./routes/devices.$id.settings.appearance";
|
import SettingsAppearanceRoute from "./routes/devices.$id.settings.appearance";
|
||||||
import * as SettingsGeneralIndexRoute from "./routes/devices.$id.settings.general._index";
|
import * as SettingsGeneralIndexRoute from "./routes/devices.$id.settings.general._index";
|
||||||
|
import SettingsGeneralRebootRoute from "./routes/devices.$id.settings.general.reboot";
|
||||||
import SettingsGeneralUpdateRoute from "./routes/devices.$id.settings.general.update";
|
import SettingsGeneralUpdateRoute from "./routes/devices.$id.settings.general.update";
|
||||||
import SettingsNetworkRoute from "./routes/devices.$id.settings.network";
|
import SettingsNetworkRoute from "./routes/devices.$id.settings.network";
|
||||||
import SecurityAccessLocalAuthRoute from "./routes/devices.$id.settings.access.local-auth";
|
import SecurityAccessLocalAuthRoute from "./routes/devices.$id.settings.access.local-auth";
|
||||||
|
@ -140,6 +141,10 @@ if (isOnDevice) {
|
||||||
index: true,
|
index: true,
|
||||||
element: <SettingsGeneralIndexRoute.default />,
|
element: <SettingsGeneralIndexRoute.default />,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "reboot",
|
||||||
|
element: <SettingsGeneralRebootRoute />,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "update",
|
path: "update",
|
||||||
element: <SettingsGeneralUpdateRoute />,
|
element: <SettingsGeneralUpdateRoute />,
|
||||||
|
|
|
@ -1,28 +0,0 @@
|
||||||
|
|
||||||
import { Checkbox } from "@/components/Checkbox";
|
|
||||||
import { SettingsPageHeader } from "@/components/SettingsPageheader";
|
|
||||||
import { useSettingsStore } from "@/hooks/stores";
|
|
||||||
|
|
||||||
import { SettingsItem } from "./devices.$id.settings";
|
|
||||||
|
|
||||||
export default function SettingsCtrlAltDelRoute() {
|
|
||||||
const enableCtrlAltDel = useSettingsStore(state => state.actionBarCtrlAltDel);
|
|
||||||
const setEnableCtrlAltDel = useSettingsStore(state => state.setActionBarCtrlAltDel);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="space-y-4">
|
|
||||||
<SettingsPageHeader
|
|
||||||
title="Action Bar"
|
|
||||||
description="Customize the action bar of your JetKVM interface"
|
|
||||||
/>
|
|
||||||
<div className="space-y-4">
|
|
||||||
<SettingsItem title="Enable Ctrl-Alt-Del" description="Enable the Ctrl-Alt-Del key on the virtual keyboard">
|
|
||||||
<Checkbox
|
|
||||||
checked={enableCtrlAltDel}
|
|
||||||
onChange={e => setEnableCtrlAltDel(e.target.checked)}
|
|
||||||
/>
|
|
||||||
</SettingsItem>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -92,6 +92,21 @@ export default function SettingsGeneralRoute() {
|
||||||
/>
|
/>
|
||||||
</SettingsItem>
|
</SettingsItem>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-2 flex items-center justify-between gap-x-2">
|
||||||
|
<SettingsItem
|
||||||
|
title="Reboot Device"
|
||||||
|
description="Power cycle the JetKVM"
|
||||||
|
/>
|
||||||
|
<div>
|
||||||
|
<Button
|
||||||
|
size="SM"
|
||||||
|
theme="light"
|
||||||
|
text="Reboot Device"
|
||||||
|
onClick={() => navigateTo("./reboot")}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -0,0 +1,66 @@
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import { useCallback } from "react";
|
||||||
|
|
||||||
|
import { useJsonRpc } from "@/hooks/useJsonRpc";
|
||||||
|
import { Button } from "@components/Button";
|
||||||
|
|
||||||
|
export default function SettingsGeneralRebootRoute() {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const [send] = useJsonRpc();
|
||||||
|
|
||||||
|
const onConfirmUpdate = useCallback(() => {
|
||||||
|
// This is where we send the RPC to the golang binary
|
||||||
|
send("reboot", {force: true});
|
||||||
|
}, [send]);
|
||||||
|
|
||||||
|
{
|
||||||
|
/* TODO: Migrate to using URLs instead of the global state. To simplify the refactoring, we'll keep the global state for now. */
|
||||||
|
}
|
||||||
|
return <Dialog onClose={() => navigate("..")} onConfirmUpdate={onConfirmUpdate} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Dialog({
|
||||||
|
onClose,
|
||||||
|
onConfirmUpdate,
|
||||||
|
}: {
|
||||||
|
onClose: () => void;
|
||||||
|
onConfirmUpdate: () => void;
|
||||||
|
}) {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="pointer-events-auto relative mx-auto text-left">
|
||||||
|
<div>
|
||||||
|
<ConfirmationBox
|
||||||
|
onYes={onConfirmUpdate}
|
||||||
|
onNo={onClose}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ConfirmationBox({
|
||||||
|
onYes,
|
||||||
|
onNo,
|
||||||
|
}: {
|
||||||
|
onYes: () => void;
|
||||||
|
onNo: () => void;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col items-start justify-start space-y-4 text-left">
|
||||||
|
<div className="text-left">
|
||||||
|
<p className="text-base font-semibold text-black dark:text-white">
|
||||||
|
Reboot JetKVM
|
||||||
|
</p>
|
||||||
|
<p className="text-sm text-slate-600 dark:text-slate-300">
|
||||||
|
Do you want to proceed with rebooting the system?
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="mt-4 flex gap-x-2">
|
||||||
|
<Button size="SM" theme="light" text="Yes" onClick={onYes} />
|
||||||
|
<Button size="SM" theme="blank" text="No" onClick={onNo} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
|
@ -116,15 +116,6 @@ export default function SettingsHardwareRoute() {
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</SettingsItem>
|
</SettingsItem>
|
||||||
{/* <SettingsItem
|
|
||||||
title="Enable Ctrl+Alt+Del Action Bar"
|
|
||||||
description="Enable or disable the action bar action for sending a Ctrl+Alt+Del to the host"
|
|
||||||
>
|
|
||||||
<Checkbox
|
|
||||||
checked={actionBarConfig.ctrlAltDel}
|
|
||||||
onChange={onActionBarItemChange("ctrlAltDel")}
|
|
||||||
/>
|
|
||||||
</SettingsItem> */}
|
|
||||||
{settings.backlightSettings.max_brightness != 0 && (
|
{settings.backlightSettings.max_brightness != 0 && (
|
||||||
<>
|
<>
|
||||||
<SettingsItem
|
<SettingsItem
|
||||||
|
|
18
web.go
18
web.go
|
@ -97,9 +97,6 @@ func setupRouter() *gin.Engine {
|
||||||
// We use this to determine if the device is setup
|
// We use this to determine if the device is setup
|
||||||
r.GET("/device/status", handleDeviceStatus)
|
r.GET("/device/status", handleDeviceStatus)
|
||||||
|
|
||||||
// We use this to provide the UI with the device configuration
|
|
||||||
r.GET("/device/ui-config.js", handleDeviceUIConfig)
|
|
||||||
|
|
||||||
// We use this to setup the device in the welcome page
|
// We use this to setup the device in the welcome page
|
||||||
r.POST("/device/setup", handleSetup)
|
r.POST("/device/setup", handleSetup)
|
||||||
|
|
||||||
|
@ -694,21 +691,6 @@ func handleCloudState(c *gin.Context) {
|
||||||
c.JSON(http.StatusOK, response)
|
c.JSON(http.StatusOK, response)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleDeviceUIConfig(c *gin.Context) {
|
|
||||||
config, _ := json.Marshal(gin.H{
|
|
||||||
"CLOUD_API": config.CloudURL,
|
|
||||||
"DEVICE_VERSION": builtAppVersion,
|
|
||||||
})
|
|
||||||
if config == nil {
|
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to marshal config"})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
response := fmt.Sprintf("window.JETKVM_CONFIG = %s;", config)
|
|
||||||
|
|
||||||
c.Data(http.StatusOK, "text/javascript; charset=utf-8", []byte(response))
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleSetup(c *gin.Context) {
|
func handleSetup(c *gin.Context) {
|
||||||
// Check if the device is already set up
|
// Check if the device is already set up
|
||||||
if config.LocalAuthMode != "" || config.HashedPassword != "" {
|
if config.LocalAuthMode != "" || config.HashedPassword != "" {
|
||||||
|
|
Loading…
Reference in New Issue