Loading apps/demo/src/components/scrcpy/command-bar.tsx +4 −7 Original line number Diff line number Diff line Loading @@ -77,13 +77,10 @@ const ITEMS = computed(() => { iconProps: { iconName: Icons.FullScreenMaximize }, iconOnly: true, text: "Fullscreen", onClick: (async () => { STATE.deviceView?.enterFullscreen(); if ("keyboard" in navigator) { // @ts-expect-error await navigator.keyboard.lock(); } }) as () => void, onClick: action(() => { STATE.fullScreenContainer?.requestFullscreen(); STATE.isFullScreen = true; }), }); result.push( Loading apps/demo/src/components/scrcpy/state.tsx +21 −11 Original line number Diff line number Diff line Loading @@ -24,7 +24,6 @@ import { import { action, autorun, makeAutoObservable, runInAction } from "mobx"; import { GLOBAL_STATE } from "../../state"; import { ProgressStream } from "../../utils"; import { DeviceViewRef } from "../device-view"; import { fetchServer } from "./fetch-server"; import { MuxerStream, RECORD_STATE } from "./recorder"; import { H264Decoder, SETTING_STATE } from "./settings"; Loading @@ -36,9 +35,11 @@ const NOOP = () => { export class ScrcpyPageState { running = false; deviceView: DeviceViewRef | null = null; fullScreenContainer: HTMLDivElement | null = null; rendererContainer: HTMLDivElement | null = null; isFullScreen = false; logVisible = false; log: string[] = []; demoModeVisible = false; Loading Loading @@ -89,8 +90,8 @@ export class ScrcpyPageState { start: false, stop: action.bound, dispose: action.bound, handleDeviceViewRef: action.bound, handleRendererContainerRef: action.bound, setFullScreenContainer: action.bound, setRendererContainer: action.bound, clientPositionToDevicePosition: false, }); Loading @@ -100,6 +101,16 @@ export class ScrcpyPageState { } }); if (typeof document === "object") { document.addEventListener("fullscreenchange", () => { if (!document.fullscreenElement) { runInAction(() => { this.isFullScreen = false; }); } }); } autorun(() => { if (this.rendererContainer && this.decoder) { while (this.rendererContainer.firstChild) { Loading Loading @@ -370,21 +381,20 @@ export class ScrcpyPageState { this.fps = "0"; clearTimeout(this.fpsCounterIntervalId); if (this.isFullScreen) { document.exitFullscreen().catch(NOOP); if ("keyboard" in navigator) { // @ts-expect-error navigator.keyboard.unlock(); this.isFullScreen = false; } this.client = undefined; this.running = false; } handleDeviceViewRef(element: DeviceViewRef | null) { this.deviceView = element; setFullScreenContainer(element: HTMLDivElement | null) { this.fullScreenContainer = element; } handleRendererContainerRef(element: HTMLDivElement | null) { setRendererContainer(element: HTMLDivElement | null) { this.rendererContainer = element; } Loading apps/demo/src/components/scrcpy/video-container.tsx +9 −4 Original line number Diff line number Diff line Loading @@ -192,10 +192,18 @@ function handleBlur() { export function VideoContainer() { const classes = useClasses(); useEffect(() => { window.addEventListener("blur", handleBlur); return () => { window.removeEventListener("blur", handleBlur); }; }, []); const [container, setContainer] = useState<HTMLDivElement | null>(null); useEffect(() => { STATE.handleRendererContainerRef(container); STATE.setRendererContainer(container); if (!container) { return; Loading @@ -205,11 +213,8 @@ export function VideoContainer() { passive: false, }); window.addEventListener("blur", handleBlur); return () => { container.removeEventListener("wheel", handleWheel); window.removeEventListener("blur", handleBlur); }; }, [container]); Loading apps/demo/src/pages/scrcpy.tsx +73 −19 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 { WebCodecsDecoder } from "@yume-chan/scrcpy-decoder-webcodecs"; import { action, runInAction } from "mobx"; import { observer } from "mobx-react-lite"; Loading @@ -16,9 +17,38 @@ import { SettingItem, VideoContainer, } from "../components/scrcpy"; import { GLOBAL_STATE } from "../state"; import { CommonStackTokens, RouteStackProps, formatSpeed } from "../utils"; const useClasses = makeStyles({ layerHost: { position: "absolute", top: 0, bottom: 0, left: 0, right: 0, pointerEvents: "none", ...shorthands.margin(0), }, fullScreenContainer: { flexGrow: 1, display: "flex", flexDirection: "column", backgroundColor: "black", }, fullScreenStatusBar: { display: "flex", color: "white", columnGap: "12px", ...shorthands.padding("8px", "20px"), }, spacer: { flexGrow: 1, }, }); const ConnectionDialog = observer(() => { const classes = useClasses(); const layerHostId = useId("layerHost"); const [isClient, setIsClient] = useState(false); Loading @@ -33,18 +63,7 @@ const ConnectionDialog = observer(() => { return ( <> <LayerHost id={layerHostId} style={{ position: "absolute", top: 0, bottom: 0, left: 0, right: 0, margin: 0, pointerEvents: "none", }} /> <LayerHost id={layerHostId} className={classes.layerHost} /> <Dialog hidden={!STATE.connecting} Loading Loading @@ -97,6 +116,8 @@ const ConnectionDialog = observer(() => { }); const Scrcpy: NextPage = () => { const classes = useClasses(); useEffect(() => { // Detect WebCodecs support at client side if ( Loading @@ -113,6 +134,25 @@ const Scrcpy: NextPage = () => { } }, []); const [keyboardLockEnabled, setKeyboardLockEnabled] = useState(false); useEffect(() => { if (!("keyboard" in navigator)) { return; } // Keyboard Lock is only effective in fullscreen mode, // but the `lock` method can be called at any time. // @ts-expect-error navigator.keyboard.lock(); setKeyboardLockEnabled(true); return () => { // @ts-expect-error navigator.keyboard.unlock(); }; }, []); return ( <Stack {...RouteStackProps}> <Head> Loading @@ -122,14 +162,28 @@ const Scrcpy: NextPage = () => { <ScrcpyCommandBar /> <Stack horizontal grow styles={{ root: { height: 0 } }}> <div ref={STATE.setFullScreenContainer} className={classes.fullScreenContainer} > {keyboardLockEnabled && STATE.isFullScreen && ( <div className={classes.fullScreenStatusBar}> <div>{GLOBAL_STATE.backend?.serial}</div> <div>FPS: {STATE.fps}</div> <div className={classes.spacer} /> <div>Press and hold ESC to exit full screen</div> </div> )} <DeviceView ref={STATE.handleDeviceViewRef} width={STATE.rotatedWidth} height={STATE.rotatedHeight} BottomElement={NavigationBar} > <VideoContainer /> </DeviceView> </div> <div style={{ Loading Loading
apps/demo/src/components/scrcpy/command-bar.tsx +4 −7 Original line number Diff line number Diff line Loading @@ -77,13 +77,10 @@ const ITEMS = computed(() => { iconProps: { iconName: Icons.FullScreenMaximize }, iconOnly: true, text: "Fullscreen", onClick: (async () => { STATE.deviceView?.enterFullscreen(); if ("keyboard" in navigator) { // @ts-expect-error await navigator.keyboard.lock(); } }) as () => void, onClick: action(() => { STATE.fullScreenContainer?.requestFullscreen(); STATE.isFullScreen = true; }), }); result.push( Loading
apps/demo/src/components/scrcpy/state.tsx +21 −11 Original line number Diff line number Diff line Loading @@ -24,7 +24,6 @@ import { import { action, autorun, makeAutoObservable, runInAction } from "mobx"; import { GLOBAL_STATE } from "../../state"; import { ProgressStream } from "../../utils"; import { DeviceViewRef } from "../device-view"; import { fetchServer } from "./fetch-server"; import { MuxerStream, RECORD_STATE } from "./recorder"; import { H264Decoder, SETTING_STATE } from "./settings"; Loading @@ -36,9 +35,11 @@ const NOOP = () => { export class ScrcpyPageState { running = false; deviceView: DeviceViewRef | null = null; fullScreenContainer: HTMLDivElement | null = null; rendererContainer: HTMLDivElement | null = null; isFullScreen = false; logVisible = false; log: string[] = []; demoModeVisible = false; Loading Loading @@ -89,8 +90,8 @@ export class ScrcpyPageState { start: false, stop: action.bound, dispose: action.bound, handleDeviceViewRef: action.bound, handleRendererContainerRef: action.bound, setFullScreenContainer: action.bound, setRendererContainer: action.bound, clientPositionToDevicePosition: false, }); Loading @@ -100,6 +101,16 @@ export class ScrcpyPageState { } }); if (typeof document === "object") { document.addEventListener("fullscreenchange", () => { if (!document.fullscreenElement) { runInAction(() => { this.isFullScreen = false; }); } }); } autorun(() => { if (this.rendererContainer && this.decoder) { while (this.rendererContainer.firstChild) { Loading Loading @@ -370,21 +381,20 @@ export class ScrcpyPageState { this.fps = "0"; clearTimeout(this.fpsCounterIntervalId); if (this.isFullScreen) { document.exitFullscreen().catch(NOOP); if ("keyboard" in navigator) { // @ts-expect-error navigator.keyboard.unlock(); this.isFullScreen = false; } this.client = undefined; this.running = false; } handleDeviceViewRef(element: DeviceViewRef | null) { this.deviceView = element; setFullScreenContainer(element: HTMLDivElement | null) { this.fullScreenContainer = element; } handleRendererContainerRef(element: HTMLDivElement | null) { setRendererContainer(element: HTMLDivElement | null) { this.rendererContainer = element; } Loading
apps/demo/src/components/scrcpy/video-container.tsx +9 −4 Original line number Diff line number Diff line Loading @@ -192,10 +192,18 @@ function handleBlur() { export function VideoContainer() { const classes = useClasses(); useEffect(() => { window.addEventListener("blur", handleBlur); return () => { window.removeEventListener("blur", handleBlur); }; }, []); const [container, setContainer] = useState<HTMLDivElement | null>(null); useEffect(() => { STATE.handleRendererContainerRef(container); STATE.setRendererContainer(container); if (!container) { return; Loading @@ -205,11 +213,8 @@ export function VideoContainer() { passive: false, }); window.addEventListener("blur", handleBlur); return () => { container.removeEventListener("wheel", handleWheel); window.removeEventListener("blur", handleBlur); }; }, [container]); Loading
apps/demo/src/pages/scrcpy.tsx +73 −19 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 { WebCodecsDecoder } from "@yume-chan/scrcpy-decoder-webcodecs"; import { action, runInAction } from "mobx"; import { observer } from "mobx-react-lite"; Loading @@ -16,9 +17,38 @@ import { SettingItem, VideoContainer, } from "../components/scrcpy"; import { GLOBAL_STATE } from "../state"; import { CommonStackTokens, RouteStackProps, formatSpeed } from "../utils"; const useClasses = makeStyles({ layerHost: { position: "absolute", top: 0, bottom: 0, left: 0, right: 0, pointerEvents: "none", ...shorthands.margin(0), }, fullScreenContainer: { flexGrow: 1, display: "flex", flexDirection: "column", backgroundColor: "black", }, fullScreenStatusBar: { display: "flex", color: "white", columnGap: "12px", ...shorthands.padding("8px", "20px"), }, spacer: { flexGrow: 1, }, }); const ConnectionDialog = observer(() => { const classes = useClasses(); const layerHostId = useId("layerHost"); const [isClient, setIsClient] = useState(false); Loading @@ -33,18 +63,7 @@ const ConnectionDialog = observer(() => { return ( <> <LayerHost id={layerHostId} style={{ position: "absolute", top: 0, bottom: 0, left: 0, right: 0, margin: 0, pointerEvents: "none", }} /> <LayerHost id={layerHostId} className={classes.layerHost} /> <Dialog hidden={!STATE.connecting} Loading Loading @@ -97,6 +116,8 @@ const ConnectionDialog = observer(() => { }); const Scrcpy: NextPage = () => { const classes = useClasses(); useEffect(() => { // Detect WebCodecs support at client side if ( Loading @@ -113,6 +134,25 @@ const Scrcpy: NextPage = () => { } }, []); const [keyboardLockEnabled, setKeyboardLockEnabled] = useState(false); useEffect(() => { if (!("keyboard" in navigator)) { return; } // Keyboard Lock is only effective in fullscreen mode, // but the `lock` method can be called at any time. // @ts-expect-error navigator.keyboard.lock(); setKeyboardLockEnabled(true); return () => { // @ts-expect-error navigator.keyboard.unlock(); }; }, []); return ( <Stack {...RouteStackProps}> <Head> Loading @@ -122,14 +162,28 @@ const Scrcpy: NextPage = () => { <ScrcpyCommandBar /> <Stack horizontal grow styles={{ root: { height: 0 } }}> <div ref={STATE.setFullScreenContainer} className={classes.fullScreenContainer} > {keyboardLockEnabled && STATE.isFullScreen && ( <div className={classes.fullScreenStatusBar}> <div>{GLOBAL_STATE.backend?.serial}</div> <div>FPS: {STATE.fps}</div> <div className={classes.spacer} /> <div>Press and hold ESC to exit full screen</div> </div> )} <DeviceView ref={STATE.handleDeviceViewRef} width={STATE.rotatedWidth} height={STATE.rotatedHeight} BottomElement={NavigationBar} > <VideoContainer /> </DeviceView> </div> <div style={{ Loading