Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 2bb2af31 authored by Simon Chan's avatar Simon Chan
Browse files

feat(demo): keep focus in device view

parent 73d2cec8
Loading
Loading
Loading
Loading
+23 −1
Original line number Diff line number Diff line
@@ -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;
                  }),
@@ -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;
                  }),
@@ -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;
        }),
    });
@@ -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,
@@ -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,
@@ -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,
@@ -158,6 +170,8 @@ const ITEMS = computed(() => {
            iconOnly: true,
            text: "Rotate Device",
            onClick: () => {
                STATE.fullScreenContainer!.focus();

                STATE.client!.controlMessageSerializer!.rotateDevice();
            },
        },
@@ -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;
@@ -181,6 +197,8 @@ const ITEMS = computed(() => {
            iconOnly: true,
            text: "Rotate Video Right",
            onClick: action(() => {
                STATE.fullScreenContainer!.focus();

                STATE.rotation = (STATE.rotation + 1) & 3;
            }),
        }
@@ -194,6 +212,8 @@ const ITEMS = computed(() => {
            iconOnly: true,
            text: "Turn Screen Off",
            onClick: () => {
                STATE.fullScreenContainer!.focus();

                STATE.client!.controlMessageSerializer!.setScreenPowerMode(
                    AndroidScreenPowerMode.Off
                );
@@ -206,6 +226,8 @@ const ITEMS = computed(() => {
            iconOnly: true,
            text: "Turn Screen On",
            onClick: () => {
                STATE.fullScreenContainer!.focus();

                STATE.client!.controlMessageSerializer!.setScreenPowerMode(
                    AndroidScreenPowerMode.Normal
                );
+0 −4
Original line number Diff line number Diff line
@@ -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;
}

+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({
@@ -82,7 +70,6 @@ function handlePointerDown(e: PointerEvent<HTMLDivElement>) {
        return;
    }

    STATE.rendererContainer!.focus();
    e.preventDefault();
    e.stopPropagation();

@@ -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(() => {
@@ -221,7 +143,6 @@ export function VideoContainer() {
    return (
        <div
            ref={setContainer}
            tabIndex={-1}
            className={classes.video}
            style={{
                width: STATE.width,
@@ -237,8 +158,6 @@ export function VideoContainer() {
            onPointerUp={handlePointerUp}
            onPointerCancel={handlePointerUp}
            onPointerLeave={handlePointerLeave}
            onKeyDown={handleKeyEvent}
            onKeyUp={handleKeyEvent}
            onContextMenu={handleContextMenu}
        />
    );
+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,
@@ -35,6 +40,9 @@ const useClasses = makeStyles({
        display: "flex",
        flexDirection: "column",
        backgroundColor: "black",
        ":focus-visible": {
            ...shorthands.outline("0"),
        },
    },
    fullScreenStatusBar: {
        display: "flex",
@@ -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();

@@ -153,6 +214,14 @@ const Scrcpy: NextPage = () => {
        };
    }, []);

    useEffect(() => {
        window.addEventListener("blur", handleBlur);

        return () => {
            window.removeEventListener("blur", handleBlur);
        };
    }, []);

    return (
        <Stack {...RouteStackProps}>
            <Head>
@@ -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}>