Loading apps/demo/src/components/scrcpy/command-bar.tsx +23 −1 Original line number Diff line number Diff line Loading @@ -55,6 +55,8 @@ const ITEMS = computed(() => { RECORD_STATE.seconds.toString().padStart(2, "0") }`, onClick: action(() => { STATE.fullScreenContainer!.focus(); RECORD_STATE.recorder.stop(); RECORD_STATE.recording = false; }), Loading @@ -65,6 +67,8 @@ const ITEMS = computed(() => { iconProps: { iconName: Icons.Record }, text: "Record", onClick: action(() => { STATE.fullScreenContainer!.focus(); RECORD_STATE.recorder.start(); RECORD_STATE.recording = true; }), Loading @@ -78,7 +82,9 @@ const ITEMS = computed(() => { iconOnly: true, text: "Fullscreen", onClick: action(() => { STATE.fullScreenContainer?.requestFullscreen(); STATE.fullScreenContainer!.focus(); STATE.fullScreenContainer!.requestFullscreen(); STATE.isFullScreen = true; }), }); Loading @@ -91,6 +97,8 @@ const ITEMS = computed(() => { iconOnly: true, text: "Volume Up", onClick: (async () => { STATE.fullScreenContainer!.focus(); // TODO: Auto repeat when holding await STATE.client?.controlMessageSerializer!.injectKeyCode({ action: AndroidKeyEventAction.Down, Loading @@ -113,6 +121,8 @@ const ITEMS = computed(() => { iconOnly: true, text: "Volume Down", onClick: (async () => { STATE.fullScreenContainer!.focus(); await STATE.client?.controlMessageSerializer!.injectKeyCode({ action: AndroidKeyEventAction.Down, keyCode: AndroidKeyCode.VolumeDown, Loading @@ -134,6 +144,8 @@ const ITEMS = computed(() => { iconOnly: true, text: "Toggle Mute", onClick: (async () => { STATE.fullScreenContainer!.focus(); await STATE.client?.controlMessageSerializer!.injectKeyCode({ action: AndroidKeyEventAction.Down, keyCode: AndroidKeyCode.VolumeMute, Loading @@ -158,6 +170,8 @@ const ITEMS = computed(() => { iconOnly: true, text: "Rotate Device", onClick: () => { STATE.fullScreenContainer!.focus(); STATE.client!.controlMessageSerializer!.rotateDevice(); }, }, Loading @@ -168,6 +182,8 @@ const ITEMS = computed(() => { iconOnly: true, text: "Rotate Video Left", onClick: action(() => { STATE.fullScreenContainer!.focus(); STATE.rotation -= 1; if (STATE.rotation < 0) { STATE.rotation = 3; Loading @@ -181,6 +197,8 @@ const ITEMS = computed(() => { iconOnly: true, text: "Rotate Video Right", onClick: action(() => { STATE.fullScreenContainer!.focus(); STATE.rotation = (STATE.rotation + 1) & 3; }), } Loading @@ -194,6 +212,8 @@ const ITEMS = computed(() => { iconOnly: true, text: "Turn Screen Off", onClick: () => { STATE.fullScreenContainer!.focus(); STATE.client!.controlMessageSerializer!.setScreenPowerMode( AndroidScreenPowerMode.Off ); Loading @@ -206,6 +226,8 @@ const ITEMS = computed(() => { iconOnly: true, text: "Turn Screen On", onClick: () => { STATE.fullScreenContainer!.focus(); STATE.client!.controlMessageSerializer!.setScreenPowerMode( AndroidScreenPowerMode.Normal ); Loading apps/demo/src/components/scrcpy/navigation-bar.tsx +0 −4 Original line number Diff line number Diff line Loading @@ -36,10 +36,6 @@ function handlePointerDown(e: PointerEvent<HTMLDivElement>) { e.preventDefault(); e.stopPropagation(); // Don't focus virtual navigation buttons // make sure all keyboard events are sent to the renderer STATE.rendererContainer!.focus(); return true; } Loading apps/demo/src/components/scrcpy/video-container.tsx +2 −83 Original line number Diff line number Diff line import { makeStyles } from "@griffel/react"; import { AndroidKeyCode, AndroidKeyEventAction, AndroidKeyEventMeta, AndroidMotionEventAction, ScrcpyPointerId, } from "@yume-chan/scrcpy"; import { KeyboardEvent, MouseEvent, PointerEvent, useEffect, useState, } from "react"; import { AndroidMotionEventAction, ScrcpyPointerId } from "@yume-chan/scrcpy"; import { MouseEvent, PointerEvent, useEffect, useState } from "react"; import { STATE } from "./state"; const useClasses = makeStyles({ Loading Loading @@ -82,7 +70,6 @@ function handlePointerDown(e: PointerEvent<HTMLDivElement>) { return; } STATE.rendererContainer!.focus(); e.preventDefault(); e.stopPropagation(); Loading Loading @@ -132,74 +119,9 @@ function handleContextMenu(e: MouseEvent<HTMLDivElement>) { e.preventDefault(); } async function handleKeyEvent(e: KeyboardEvent<HTMLDivElement>) { if (!STATE.client) { return; } e.preventDefault(); e.stopPropagation(); const { repeat, type, code } = e; if (repeat) { return; } const keyCode = AndroidKeyCode[code as keyof typeof AndroidKeyCode]; if (keyCode) { if (type === "keydown") { STATE.pressedKeys.add(keyCode); } else { STATE.pressedKeys.delete(keyCode); } // TODO: workaround the missing keyup event on macOS https://crbug.com/1393524 STATE.client!.controlMessageSerializer!.injectKeyCode({ action: type === "keydown" ? AndroidKeyEventAction.Down : AndroidKeyEventAction.Up, keyCode, metaState: (e.ctrlKey ? AndroidKeyEventMeta.CtrlOn : 0) | (e.shiftKey ? AndroidKeyEventMeta.ShiftOn : 0) | (e.altKey ? AndroidKeyEventMeta.AltOn : 0) | (e.metaKey ? AndroidKeyEventMeta.MetaOn : 0), repeat: 0, }); } } function handleBlur() { if (!STATE.client) { return; } // Release all pressed keys on window blur, // Because there will not be any keyup events when window is not focused. for (const key of STATE.pressedKeys) { STATE.client.controlMessageSerializer!.injectKeyCode({ action: AndroidKeyEventAction.Up, keyCode: key, metaState: 0, repeat: 0, }); } STATE.pressedKeys.clear(); } export function VideoContainer() { const classes = useClasses(); useEffect(() => { window.addEventListener("blur", handleBlur); return () => { window.removeEventListener("blur", handleBlur); }; }, []); const [container, setContainer] = useState<HTMLDivElement | null>(null); useEffect(() => { Loading @@ -221,7 +143,6 @@ export function VideoContainer() { return ( <div ref={setContainer} tabIndex={-1} className={classes.video} style={{ width: STATE.width, Loading @@ -237,8 +158,6 @@ export function VideoContainer() { onPointerUp={handlePointerUp} onPointerCancel={handlePointerUp} onPointerLeave={handlePointerLeave} onKeyDown={handleKeyEvent} onKeyUp={handleKeyEvent} onContextMenu={handleContextMenu} /> ); Loading apps/demo/src/pages/scrcpy.tsx +73 −1 Original line number Diff line number Diff line import { Dialog, LayerHost, ProgressIndicator, Stack } from "@fluentui/react"; import { useId } from "@fluentui/react-hooks"; import { makeStyles, shorthands } from "@griffel/react"; import { AndroidKeyCode, AndroidKeyEventAction, AndroidKeyEventMeta, } from "@yume-chan/scrcpy"; import { WebCodecsDecoder } from "@yume-chan/scrcpy-decoder-webcodecs"; import { action, runInAction } from "mobx"; import { observer } from "mobx-react-lite"; import { NextPage } from "next"; import Head from "next/head"; import { useEffect, useState } from "react"; import { KeyboardEvent, useEffect, useState } from "react"; import { DemoModePanel, DeviceView } from "../components"; import { NavigationBar, Loading Loading @@ -35,6 +40,9 @@ const useClasses = makeStyles({ display: "flex", flexDirection: "column", backgroundColor: "black", ":focus-visible": { ...shorthands.outline("0"), }, }, fullScreenStatusBar: { display: "flex", Loading Loading @@ -115,6 +123,59 @@ const ConnectionDialog = observer(() => { ); }); async function handleKeyEvent(e: KeyboardEvent<HTMLDivElement>) { if (!STATE.client) { return; } e.preventDefault(); e.stopPropagation(); const { type, code } = e; const keyCode = AndroidKeyCode[code as keyof typeof AndroidKeyCode]; if (keyCode) { if (type === "keydown") { STATE.pressedKeys.add(keyCode); } else { STATE.pressedKeys.delete(keyCode); } // TODO: workaround the missing keyup event on macOS https://crbug.com/1393524 STATE.client!.controlMessageSerializer!.injectKeyCode({ action: type === "keydown" ? AndroidKeyEventAction.Down : AndroidKeyEventAction.Up, keyCode, metaState: (e.ctrlKey ? AndroidKeyEventMeta.CtrlOn : 0) | (e.shiftKey ? AndroidKeyEventMeta.ShiftOn : 0) | (e.altKey ? AndroidKeyEventMeta.AltOn : 0) | (e.metaKey ? AndroidKeyEventMeta.MetaOn : 0), repeat: 0, }); } } function handleBlur() { if (!STATE.client) { return; } // Release all pressed keys on window blur, // Because there will not be any keyup events when window is not focused. for (const key of STATE.pressedKeys) { STATE.client.controlMessageSerializer!.injectKeyCode({ action: AndroidKeyEventAction.Up, keyCode: key, metaState: 0, repeat: 0, }); } STATE.pressedKeys.clear(); } const Scrcpy: NextPage = () => { const classes = useClasses(); Loading Loading @@ -153,6 +214,14 @@ const Scrcpy: NextPage = () => { }; }, []); useEffect(() => { window.addEventListener("blur", handleBlur); return () => { window.removeEventListener("blur", handleBlur); }; }, []); return ( <Stack {...RouteStackProps}> <Head> Loading @@ -165,6 +234,9 @@ const Scrcpy: NextPage = () => { <div ref={STATE.setFullScreenContainer} className={classes.fullScreenContainer} tabIndex={0} onKeyDown={handleKeyEvent} onKeyUp={handleKeyEvent} > {keyboardLockEnabled && STATE.isFullScreen && ( <div className={classes.fullScreenStatusBar}> Loading Loading
apps/demo/src/components/scrcpy/command-bar.tsx +23 −1 Original line number Diff line number Diff line Loading @@ -55,6 +55,8 @@ const ITEMS = computed(() => { RECORD_STATE.seconds.toString().padStart(2, "0") }`, onClick: action(() => { STATE.fullScreenContainer!.focus(); RECORD_STATE.recorder.stop(); RECORD_STATE.recording = false; }), Loading @@ -65,6 +67,8 @@ const ITEMS = computed(() => { iconProps: { iconName: Icons.Record }, text: "Record", onClick: action(() => { STATE.fullScreenContainer!.focus(); RECORD_STATE.recorder.start(); RECORD_STATE.recording = true; }), Loading @@ -78,7 +82,9 @@ const ITEMS = computed(() => { iconOnly: true, text: "Fullscreen", onClick: action(() => { STATE.fullScreenContainer?.requestFullscreen(); STATE.fullScreenContainer!.focus(); STATE.fullScreenContainer!.requestFullscreen(); STATE.isFullScreen = true; }), }); Loading @@ -91,6 +97,8 @@ const ITEMS = computed(() => { iconOnly: true, text: "Volume Up", onClick: (async () => { STATE.fullScreenContainer!.focus(); // TODO: Auto repeat when holding await STATE.client?.controlMessageSerializer!.injectKeyCode({ action: AndroidKeyEventAction.Down, Loading @@ -113,6 +121,8 @@ const ITEMS = computed(() => { iconOnly: true, text: "Volume Down", onClick: (async () => { STATE.fullScreenContainer!.focus(); await STATE.client?.controlMessageSerializer!.injectKeyCode({ action: AndroidKeyEventAction.Down, keyCode: AndroidKeyCode.VolumeDown, Loading @@ -134,6 +144,8 @@ const ITEMS = computed(() => { iconOnly: true, text: "Toggle Mute", onClick: (async () => { STATE.fullScreenContainer!.focus(); await STATE.client?.controlMessageSerializer!.injectKeyCode({ action: AndroidKeyEventAction.Down, keyCode: AndroidKeyCode.VolumeMute, Loading @@ -158,6 +170,8 @@ const ITEMS = computed(() => { iconOnly: true, text: "Rotate Device", onClick: () => { STATE.fullScreenContainer!.focus(); STATE.client!.controlMessageSerializer!.rotateDevice(); }, }, Loading @@ -168,6 +182,8 @@ const ITEMS = computed(() => { iconOnly: true, text: "Rotate Video Left", onClick: action(() => { STATE.fullScreenContainer!.focus(); STATE.rotation -= 1; if (STATE.rotation < 0) { STATE.rotation = 3; Loading @@ -181,6 +197,8 @@ const ITEMS = computed(() => { iconOnly: true, text: "Rotate Video Right", onClick: action(() => { STATE.fullScreenContainer!.focus(); STATE.rotation = (STATE.rotation + 1) & 3; }), } Loading @@ -194,6 +212,8 @@ const ITEMS = computed(() => { iconOnly: true, text: "Turn Screen Off", onClick: () => { STATE.fullScreenContainer!.focus(); STATE.client!.controlMessageSerializer!.setScreenPowerMode( AndroidScreenPowerMode.Off ); Loading @@ -206,6 +226,8 @@ const ITEMS = computed(() => { iconOnly: true, text: "Turn Screen On", onClick: () => { STATE.fullScreenContainer!.focus(); STATE.client!.controlMessageSerializer!.setScreenPowerMode( AndroidScreenPowerMode.Normal ); Loading
apps/demo/src/components/scrcpy/navigation-bar.tsx +0 −4 Original line number Diff line number Diff line Loading @@ -36,10 +36,6 @@ function handlePointerDown(e: PointerEvent<HTMLDivElement>) { e.preventDefault(); e.stopPropagation(); // Don't focus virtual navigation buttons // make sure all keyboard events are sent to the renderer STATE.rendererContainer!.focus(); return true; } Loading
apps/demo/src/components/scrcpy/video-container.tsx +2 −83 Original line number Diff line number Diff line import { makeStyles } from "@griffel/react"; import { AndroidKeyCode, AndroidKeyEventAction, AndroidKeyEventMeta, AndroidMotionEventAction, ScrcpyPointerId, } from "@yume-chan/scrcpy"; import { KeyboardEvent, MouseEvent, PointerEvent, useEffect, useState, } from "react"; import { AndroidMotionEventAction, ScrcpyPointerId } from "@yume-chan/scrcpy"; import { MouseEvent, PointerEvent, useEffect, useState } from "react"; import { STATE } from "./state"; const useClasses = makeStyles({ Loading Loading @@ -82,7 +70,6 @@ function handlePointerDown(e: PointerEvent<HTMLDivElement>) { return; } STATE.rendererContainer!.focus(); e.preventDefault(); e.stopPropagation(); Loading Loading @@ -132,74 +119,9 @@ function handleContextMenu(e: MouseEvent<HTMLDivElement>) { e.preventDefault(); } async function handleKeyEvent(e: KeyboardEvent<HTMLDivElement>) { if (!STATE.client) { return; } e.preventDefault(); e.stopPropagation(); const { repeat, type, code } = e; if (repeat) { return; } const keyCode = AndroidKeyCode[code as keyof typeof AndroidKeyCode]; if (keyCode) { if (type === "keydown") { STATE.pressedKeys.add(keyCode); } else { STATE.pressedKeys.delete(keyCode); } // TODO: workaround the missing keyup event on macOS https://crbug.com/1393524 STATE.client!.controlMessageSerializer!.injectKeyCode({ action: type === "keydown" ? AndroidKeyEventAction.Down : AndroidKeyEventAction.Up, keyCode, metaState: (e.ctrlKey ? AndroidKeyEventMeta.CtrlOn : 0) | (e.shiftKey ? AndroidKeyEventMeta.ShiftOn : 0) | (e.altKey ? AndroidKeyEventMeta.AltOn : 0) | (e.metaKey ? AndroidKeyEventMeta.MetaOn : 0), repeat: 0, }); } } function handleBlur() { if (!STATE.client) { return; } // Release all pressed keys on window blur, // Because there will not be any keyup events when window is not focused. for (const key of STATE.pressedKeys) { STATE.client.controlMessageSerializer!.injectKeyCode({ action: AndroidKeyEventAction.Up, keyCode: key, metaState: 0, repeat: 0, }); } STATE.pressedKeys.clear(); } export function VideoContainer() { const classes = useClasses(); useEffect(() => { window.addEventListener("blur", handleBlur); return () => { window.removeEventListener("blur", handleBlur); }; }, []); const [container, setContainer] = useState<HTMLDivElement | null>(null); useEffect(() => { Loading @@ -221,7 +143,6 @@ export function VideoContainer() { return ( <div ref={setContainer} tabIndex={-1} className={classes.video} style={{ width: STATE.width, Loading @@ -237,8 +158,6 @@ export function VideoContainer() { onPointerUp={handlePointerUp} onPointerCancel={handlePointerUp} onPointerLeave={handlePointerLeave} onKeyDown={handleKeyEvent} onKeyUp={handleKeyEvent} onContextMenu={handleContextMenu} /> ); Loading
apps/demo/src/pages/scrcpy.tsx +73 −1 Original line number Diff line number Diff line import { Dialog, LayerHost, ProgressIndicator, Stack } from "@fluentui/react"; import { useId } from "@fluentui/react-hooks"; import { makeStyles, shorthands } from "@griffel/react"; import { AndroidKeyCode, AndroidKeyEventAction, AndroidKeyEventMeta, } from "@yume-chan/scrcpy"; import { WebCodecsDecoder } from "@yume-chan/scrcpy-decoder-webcodecs"; import { action, runInAction } from "mobx"; import { observer } from "mobx-react-lite"; import { NextPage } from "next"; import Head from "next/head"; import { useEffect, useState } from "react"; import { KeyboardEvent, useEffect, useState } from "react"; import { DemoModePanel, DeviceView } from "../components"; import { NavigationBar, Loading Loading @@ -35,6 +40,9 @@ const useClasses = makeStyles({ display: "flex", flexDirection: "column", backgroundColor: "black", ":focus-visible": { ...shorthands.outline("0"), }, }, fullScreenStatusBar: { display: "flex", Loading Loading @@ -115,6 +123,59 @@ const ConnectionDialog = observer(() => { ); }); async function handleKeyEvent(e: KeyboardEvent<HTMLDivElement>) { if (!STATE.client) { return; } e.preventDefault(); e.stopPropagation(); const { type, code } = e; const keyCode = AndroidKeyCode[code as keyof typeof AndroidKeyCode]; if (keyCode) { if (type === "keydown") { STATE.pressedKeys.add(keyCode); } else { STATE.pressedKeys.delete(keyCode); } // TODO: workaround the missing keyup event on macOS https://crbug.com/1393524 STATE.client!.controlMessageSerializer!.injectKeyCode({ action: type === "keydown" ? AndroidKeyEventAction.Down : AndroidKeyEventAction.Up, keyCode, metaState: (e.ctrlKey ? AndroidKeyEventMeta.CtrlOn : 0) | (e.shiftKey ? AndroidKeyEventMeta.ShiftOn : 0) | (e.altKey ? AndroidKeyEventMeta.AltOn : 0) | (e.metaKey ? AndroidKeyEventMeta.MetaOn : 0), repeat: 0, }); } } function handleBlur() { if (!STATE.client) { return; } // Release all pressed keys on window blur, // Because there will not be any keyup events when window is not focused. for (const key of STATE.pressedKeys) { STATE.client.controlMessageSerializer!.injectKeyCode({ action: AndroidKeyEventAction.Up, keyCode: key, metaState: 0, repeat: 0, }); } STATE.pressedKeys.clear(); } const Scrcpy: NextPage = () => { const classes = useClasses(); Loading Loading @@ -153,6 +214,14 @@ const Scrcpy: NextPage = () => { }; }, []); useEffect(() => { window.addEventListener("blur", handleBlur); return () => { window.removeEventListener("blur", handleBlur); }; }, []); return ( <Stack {...RouteStackProps}> <Head> Loading @@ -165,6 +234,9 @@ const Scrcpy: NextPage = () => { <div ref={STATE.setFullScreenContainer} className={classes.fullScreenContainer} tabIndex={0} onKeyDown={handleKeyEvent} onKeyUp={handleKeyEvent} > {keyboardLockEnabled && STATE.isFullScreen && ( <div className={classes.fullScreenStatusBar}> Loading