Skip to content

Commit cfa2ea8

Browse files
jlianadamshiervani
authored andcommitted
feat(ui): add wake host button and sleep hint to no-signal overlay
When no HDMI signal is detected, the overlay now shows: - A hint that the target computer may be sleeping - A "Wake Host" button that sends a spacebar press/release via HID This helps users discover that they can wake a sleeping host directly from the JetKVM web interface, complementing the USB remote wakeup kernel support.
1 parent 9699074 commit cfa2ea8

3 files changed

Lines changed: 43 additions & 4 deletions

File tree

ui/localization/messages/en.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1093,5 +1093,8 @@
10931093
"wake_on_lan_invalid_mac": "Invalid MAC address",
10941094
"wake_on_lan_magic_sent_success": "Magic Packet sent successfully",
10951095
"welcome_to_jetkvm": "Welcome to JetKVM",
1096-
"welcome_to_jetkvm_description": "Control any computer remotely"
1096+
"welcome_to_jetkvm_description": "Control any computer remotely",
1097+
"video_overlay_no_hdmi_try_wake": "If the target computer is sleeping, you can try waking it with a simulated keyboard press",
1098+
"video_overlay_no_hdmi_wake_host": "Try Wake Host",
1099+
"video_overlay_no_hdmi_wake_host_sending": "Sending wake signal..."
10971100
}

ui/src/components/VideoOverlay.tsx

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -216,9 +216,11 @@ export function PeerConnectionDisconnectedOverlay({ show }: PeerConnectionDiscon
216216
interface HDMIErrorOverlayProps {
217217
readonly show: boolean;
218218
readonly hdmiState: string;
219+
readonly onWakeHost?: () => void;
220+
readonly isWaking?: boolean;
219221
}
220222

221-
export function HDMIErrorOverlay({ show, hdmiState }: HDMIErrorOverlayProps) {
223+
export function HDMIErrorOverlay({ show, hdmiState, onWakeHost, isWaking }: HDMIErrorOverlayProps) {
222224
const isNoSignal = hdmiState === "no_signal";
223225
const isOtherError = hdmiState === "no_lock" || hdmiState === "out_of_range";
224226

@@ -247,16 +249,30 @@ export function HDMIErrorOverlay({ show, hdmiState }: HDMIErrorOverlayProps) {
247249
<li>{m.video_overlay_no_hdmi_ensure_cable()}</li>
248250
<li>{m.video_overlay_no_hdmi_ensure_power()}</li>
249251
<li>{m.video_overlay_no_hdmi_adapter_compat()}</li>
252+
<li>{m.video_overlay_no_hdmi_try_wake()}</li>
250253
</ul>
251254
</div>
252-
<div>
255+
<div className="flex items-center gap-x-2">
253256
<LinkButton
254257
to={"https://jetkvm.com/docs/getting-started/troubleshooting"}
255258
theme="light"
256259
text={m.video_overlay_learn_more()}
257260
TrailingIcon={ArrowRightIcon}
258261
size="SM"
259262
/>
263+
{onWakeHost && (
264+
<Button
265+
onClick={onWakeHost}
266+
text={
267+
isWaking
268+
? m.video_overlay_no_hdmi_wake_host_sending()
269+
: m.video_overlay_no_hdmi_wake_host()
270+
}
271+
size="SM"
272+
theme="primary"
273+
disabled={isWaking}
274+
/>
275+
)}
260276
</div>
261277
</div>
262278
</div>

ui/src/components/WebRTCVideo.tsx

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { isWindows } from "@/utils";
66
import useKeyboard from "@hooks/useKeyboard";
77
import useMouse from "@hooks/useMouse";
88
import { useRTCStore, useSettingsStore, useUiStore, useVideoStore } from "@hooks/stores";
9+
import { useJsonRpc } from "@hooks/useJsonRpc";
910
import VirtualKeyboard from "@components/VirtualKeyboard";
1011
import Actionbar from "@components/ActionBar";
1112
import MacroBar from "@components/MacroBar";
@@ -30,6 +31,8 @@ export default function WebRTCVideo({
3031
}) {
3132
// Video and stream related refs and states
3233
const videoElm = useRef<HTMLVideoElement>(null);
34+
const [isWaking, setIsWaking] = useState(false);
35+
const { send } = useJsonRpc();
3336
const fullscreenContainerRef = useRef<HTMLDivElement>(null);
3437
const { mediaStream, peerConnectionState } = useRTCStore();
3538
const [isPlaying, setIsPlaying] = useState(false);
@@ -39,6 +42,18 @@ export default function WebRTCVideo({
3942
const isPointerLockPossible =
4043
window.location.protocol === "https:" || window.location.hostname === "localhost";
4144

45+
// Wake host handler - sends a spacebar press+release to wake sleeping host
46+
const handleWakeHost = useCallback(() => {
47+
setIsWaking(true);
48+
// Send spacebar (HID usage 0x2C) press
49+
send("keyboardReport", { keys: [0x2c, 0, 0, 0, 0, 0], modifier: 0 }, () => {
50+
// Send key release
51+
send("keyboardReport", { keys: [0, 0, 0, 0, 0, 0], modifier: 0 }, () => {
52+
setTimeout(() => setIsWaking(false), 3000);
53+
});
54+
});
55+
}, [send]);
56+
4257
// Store hooks
4358
const settings = useSettingsStore();
4459
const { handleKeyPress, resetKeyboardState } = useKeyboard();
@@ -675,7 +690,12 @@ export default function WebRTCVideo({
675690
>
676691
<div className="relative h-full w-full rounded-md">
677692
<LoadingVideoOverlay show={isVideoLoading} />
678-
<HDMIErrorOverlay show={hdmiError} hdmiState={hdmiState} />
693+
<HDMIErrorOverlay
694+
show={hdmiError}
695+
hdmiState={hdmiState}
696+
onWakeHost={handleWakeHost}
697+
isWaking={isWaking}
698+
/>
679699
<NoAutoplayPermissionsOverlay
680700
show={hasNoAutoPlayPermissions}
681701
onPlayClick={() => {

0 commit comments

Comments
 (0)