Skip to content

Commit cf71baa

Browse files
committed
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 ccde02b commit cf71baa

3 files changed

Lines changed: 34 additions & 4 deletions

File tree

ui/localization/messages/en.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -935,5 +935,8 @@
935935
"wake_on_lan_invalid_mac": "Invalid MAC address",
936936
"wake_on_lan_magic_sent_success": "Magic Packet sent successfully",
937937
"welcome_to_jetkvm": "Welcome to JetKVM",
938-
"welcome_to_jetkvm_description": "Control any computer remotely"
938+
"welcome_to_jetkvm_description": "Control any computer remotely",
939+
"video_overlay_no_hdmi_try_wake": "If the target computer is sleeping, you can try waking it with a simulated keyboard press",
940+
"video_overlay_no_hdmi_wake_host": "Try Wake Host",
941+
"video_overlay_no_hdmi_wake_host_sending": "Sending wake signal..."
939942
}

ui/src/components/VideoOverlay.tsx

Lines changed: 14 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,26 @@ 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={isWaking ? m.video_overlay_no_hdmi_wake_host_sending() : m.video_overlay_no_hdmi_wake_host()}
267+
size="SM"
268+
theme="primary"
269+
disabled={isWaking}
270+
/>
271+
)}
260272
</div>
261273
</div>
262274
</div>

ui/src/components/WebRTCVideo.tsx

Lines changed: 16 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, 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";
@@ -23,6 +24,8 @@ import { m } from "@localizations/messages.js";
2324
export default function WebRTCVideo({ hasConnectionIssues }: { hasConnectionIssues: boolean }) {
2425
// Video and stream related refs and states
2526
const videoElm = useRef<HTMLVideoElement>(null);
27+
const [isWaking, setIsWaking] = useState(false);
28+
const { send } = useJsonRpc();
2629
const fullscreenContainerRef = useRef<HTMLDivElement>(null);
2730
const { mediaStream, peerConnectionState } = useRTCStore();
2831
const [isPlaying, setIsPlaying] = useState(false);
@@ -32,6 +35,18 @@ export default function WebRTCVideo({ hasConnectionIssues }: { hasConnectionIssu
3235
const isPointerLockPossible =
3336
window.location.protocol === "https:" || window.location.hostname === "localhost";
3437

38+
// Wake host handler - sends a spacebar press+release to wake sleeping host
39+
const handleWakeHost = useCallback(() => {
40+
setIsWaking(true);
41+
// Send spacebar (HID usage 0x2C) press
42+
send("keyboardReport", { keys: [0x2C, 0, 0, 0, 0, 0], modifier: 0 }, () => {
43+
// Send key release
44+
send("keyboardReport", { keys: [0, 0, 0, 0, 0, 0], modifier: 0 }, () => {
45+
setTimeout(() => setIsWaking(false), 3000);
46+
});
47+
});
48+
}, [send]);
49+
3550
// Store hooks
3651
const settings = useSettingsStore();
3752
const { handleKeyPress, resetKeyboardState } = useKeyboard();
@@ -640,7 +655,7 @@ export default function WebRTCVideo({ hasConnectionIssues }: { hasConnectionIssu
640655
>
641656
<div className="relative h-full w-full rounded-md">
642657
<LoadingVideoOverlay show={isVideoLoading} />
643-
<HDMIErrorOverlay show={hdmiError} hdmiState={hdmiState} />
658+
<HDMIErrorOverlay show={hdmiError} hdmiState={hdmiState} onWakeHost={handleWakeHost} isWaking={isWaking} />
644659
<NoAutoplayPermissionsOverlay
645660
show={hasNoAutoPlayPermissions}
646661
onPlayClick={() => {

0 commit comments

Comments
 (0)