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

Unverified Commit d4e06cd3 authored by Simon Chan's avatar Simon Chan
Browse files

feat(scrcpy): support set screen power mode control message

fixes #428
parent 6b76f344
Loading
Loading
Loading
Loading
+92 −56
Original line number Diff line number Diff line
@@ -9,7 +9,7 @@ import { CSSProperties, ReactNode, useEffect, useState } from "react";

import { ADB_SYNC_MAX_PACKET_SIZE } from '@yume-chan/adb';
import { EventEmitter } from "@yume-chan/event";
import { AdbScrcpyClient, AdbScrcpyOptions1_22, AndroidKeyCode, AndroidKeyEventAction, AndroidMotionEventAction, CodecOptions, DEFAULT_SERVER_PATH, ScrcpyDeviceMessageType, ScrcpyLogLevel, ScrcpyOptions1_24, ScrcpyVideoOrientation, TinyH264Decoder, WebCodecsDecoder, type H264Decoder, type H264DecoderConstructor, type ScrcpyVideoStreamPacket } from "@yume-chan/scrcpy";
import { AdbScrcpyClient, AdbScrcpyOptions1_22, AndroidKeyCode, AndroidKeyEventAction, AndroidMotionEventAction, AndroidScreenPowerMode, CodecOptions, DEFAULT_SERVER_PATH, ScrcpyDeviceMessageType, ScrcpyLogLevel, ScrcpyOptions1_24, ScrcpyOptionsInit1_24, ScrcpyVideoOrientation, TinyH264Decoder, WebCodecsDecoder, type H264Decoder, type H264DecoderConstructor, type ScrcpyVideoStreamPacket } from "@yume-chan/scrcpy";
import SCRCPY_SERVER_VERSION from '@yume-chan/scrcpy/bin/version';
import { ChunkStream, InspectStream, ReadableStream, WritableStream } from '@yume-chan/stream-extra';

@@ -98,17 +98,14 @@ interface DecoderDefinition {
    Constructor: H264DecoderConstructor;
}

interface Settings {
    maxSize: number;
    bitRate: number;
    tunnelForward?: boolean;
    encoderName?: string;
type RequiredScrcpyOptions = Pick<ScrcpyOptionsInit1_24, 'crop' | 'maxSize' | 'bitRate' | 'powerOn'>;
type OptionalScrcpyOptions = Partial<Pick<ScrcpyOptionsInit1_24, 'displayId' | 'lockVideoOrientation' | 'encoderName' | 'tunnelForward' | 'stayAwake' | 'powerOffOnClose'>>;

interface Settings extends RequiredScrcpyOptions, OptionalScrcpyOptions {
    turnScreenOff?: boolean;
    decoder?: string;
    ignoreDecoderCodecArgs?: boolean;
    lockVideoOrientation?: ScrcpyVideoOrientation;
    displayId?: number;
    crop: string;
}
};

interface SettingDefinitionBase {
    key: keyof Settings;
@@ -364,9 +361,7 @@ class ScrcpyPageState {
            iconOnly: true,
            text: 'Rotate Device',
            onClick: () => { this.client!.controlMessageSerializer!.rotateDevice(); },
        });

        result.push({
        }, {
            key: 'rotateVideoLeft',
            disabled: !this.running,
            iconProps: { iconName: Icons.RotateLeft },
@@ -378,9 +373,7 @@ class ScrcpyPageState {
                    this.rotate = 3;
                }
            }
        });

        result.push({
        }, {
            key: 'rotateVideoRight',
            disabled: !this.running,
            iconProps: { iconName: Icons.RotateRight },
@@ -391,6 +384,26 @@ class ScrcpyPageState {
            },
        });

        result.push({
            key: 'turnScreenOff',
            disabled: !this.running,
            iconProps: { iconName: Icons.Lightbulb },
            iconOnly: true,
            text: 'Turn Screen Off',
            onClick: () => {
                this.client!.controlMessageSerializer!.setScreenPowerMode(AndroidScreenPowerMode.Off);
            },
        }, {
            key: 'turnScreenOn',
            disabled: !this.running,
            iconProps: { iconName: Icons.LightbulbFilament },
            iconOnly: true,
            text: 'Turn Screen On',
            onClick: () => {
                this.client!.controlMessageSerializer!.setScreenPowerMode(AndroidScreenPowerMode.Normal);
            },
        });

        if (this.running) {
            result.push({
                key: 'fps',
@@ -474,48 +487,54 @@ class ScrcpyPageState {
        lockVideoOrientation: ScrcpyVideoOrientation.Unlocked,
        displayId: 0,
        crop: '',
        powerOn: true,
    };

    get settingDefinitions() {
        const result: SettingDefinition[] = [];

        result.push({
            key: 'encoderName',
            key: 'powerOn',
            type: 'toggle',
            label: 'Turn device on when starting',
        }, {
            key: 'turnScreenOff',
            type: 'toggle',
            label: 'Turn screen off when starting',
        }, {
            key: 'stayAwake',
            type: 'toggle',
            label: 'Stay awake (if plugged in)',
        }, {
            key: 'powerOffOnClose',
            type: 'toggle',
            label: 'Turn device off when exiting',
        });

        result.push({
            key: 'displayId',
            type: 'dropdown',
            label: 'Encoder',
            placeholder: 'Press refresh to update available encoders',
            label: 'Display',
            placeholder: 'Press refresh to update available displays',
            labelExtra: (
                <IconButton
                    iconProps={{ iconName: Icons.ArrowClockwise }}
                    disabled={!GlobalState.device}
                    text="Refresh"
                    onClick={this.updateEncoders}
                    onClick={this.updateDisplays}
                />
            ),
            options: this.encoders.map(item => ({
            options: this.displays.map(item => ({
                key: item,
                text: item,
            })),
        });

        if (this.decoders.length > 1) {
            result.push({
                key: 'decoder',
                type: 'dropdown',
                label: 'Decoder',
                options: this.decoders.map(item => ({
                    key: item.key,
                    text: item.name,
                    data: item,
                text: item.toString(),
            })),
        });
        }

        result.push({
            key: 'ignoreDecoderCodecArgs',
            type: 'toggle',
            label: `Ignore decoder's codec arguments`,
            description: `Some decoders don't support all H.264 profile/levels, so they request the device to encode at their highest-supported codec. However, some super old devices may not support that codec so their encoders will fail to start. Use this option to let device choose the codec to be used.`,
            key: 'crop',
            type: 'text',
            label: 'Crop',
            placeholder: 'W:H:X:Y',
        });

        result.push({
@@ -536,13 +555,6 @@ class ScrcpyPageState {
            step: 100,
        });

        result.push({
            key: 'tunnelForward',
            type: 'toggle',
            label: 'Use forward connection',
            description: 'Android before version 9 has a bug that prevents reverse tunneling when using ADB over WiFi.'
        });

        result.push({
            key: 'lockVideoOrientation',
            type: 'dropdown',
@@ -576,29 +588,49 @@ class ScrcpyPageState {
        });

        result.push({
            key: 'displayId',
            key: 'encoderName',
            type: 'dropdown',
            label: 'Display',
            placeholder: 'Press refresh to update available displays',
            label: 'Encoder',
            placeholder: 'Press refresh to update available encoders',
            labelExtra: (
                <IconButton
                    iconProps={{ iconName: Icons.ArrowClockwise }}
                    disabled={!GlobalState.device}
                    text="Refresh"
                    onClick={this.updateDisplays}
                    onClick={this.updateEncoders}
                />
            ),
            options: this.displays.map(item => ({
            options: this.encoders.map(item => ({
                key: item,
                text: item.toString(),
                text: item,
            })),
        });

        if (this.decoders.length > 1) {
            result.push({
            key: 'crop',
            type: 'text',
            label: 'Crop',
            placeholder: 'W:H:X:Y',
                key: 'decoder',
                type: 'dropdown',
                label: 'Decoder',
                options: this.decoders.map(item => ({
                    key: item.key,
                    text: item.name,
                    data: item,
                })),
            });
        }

        result.push({
            key: 'ignoreDecoderCodecArgs',
            type: 'toggle',
            label: `Ignore decoder's codec arguments`,
            description: `Some decoders don't support all H.264 profile/levels, so they request the device to encode at their highest-supported codec. However, some super old devices may not support that codec so their encoders will fail to start. Use this option to let device choose the codec to be used.`,
        });

        result.push({
            key: 'tunnelForward',
            type: 'toggle',
            label: 'Use forward connection',
            description: 'Android before version 9 has a bug that prevents reverse tunneling when using ADB over WiFi.'
        });

        return result;
@@ -802,6 +834,10 @@ class ScrcpyPageState {
                }
            })).catch(() => { });

            if (this.settings.turnScreenOff) {
                await client.controlMessageSerializer!.setScreenPowerMode(AndroidScreenPowerMode.Off);
            }

            runInAction(() => {
                this.client = client;
                this.running = true;
+8 −2
Original line number Diff line number Diff line
import { registerIcons } from "@fluentui/react";
import { BookSearchRegular, AddCircleRegular, ArrowClockwiseRegular, ArrowRotateClockwiseRegular, ArrowRotateCounterclockwiseRegular, ArrowSortDownRegular, ArrowSortUpRegular, BookmarkRegular, BoxRegular, BugRegular, CameraRegular, CheckmarkRegular, ChevronDownRegular, ChevronRightRegular, ChevronUpRegular, CircleRegular, CloudArrowDownRegular, CloudArrowUpRegular, CopyRegular, DeleteRegular, DocumentRegular, FilterRegular, FolderRegular, FullScreenMaximizeRegular, InfoRegular, MoreHorizontalRegular, NavigationRegular, OrientationRegular, PanelBottomRegular, PersonFeedbackRegular, PhoneLaptopRegular, PhoneRegular, PlayRegular, PlugConnectedRegular, PlugDisconnectedRegular, PowerRegular, SaveRegular, SearchRegular, SettingsRegular, StopRegular, TextGrammarErrorRegular, WandRegular, WarningRegular, WifiSettingsRegular, WindowConsoleRegular } from '@fluentui/react-icons';
import { AddCircleRegular, ArrowClockwiseRegular, ArrowRotateClockwiseRegular, ArrowRotateCounterclockwiseRegular, ArrowSortDownRegular, ArrowSortUpRegular, BookmarkRegular, BookSearchRegular, BoxRegular, BugRegular, CameraRegular, CheckmarkRegular, ChevronDownRegular, ChevronRightRegular, ChevronUpRegular, CircleRegular, CloudArrowDownRegular, CloudArrowUpRegular, CopyRegular, DeleteRegular, DocumentRegular, FilterRegular, FolderRegular, FullScreenMaximizeRegular, InfoRegular, LightbulbFilamentRegular, LightbulbRegular, MoreHorizontalRegular, NavigationRegular, OrientationRegular, PanelBottomRegular, PersonFeedbackRegular, PhoneLaptopRegular, PhoneRegular, PlayRegular, PlugConnectedRegular, PlugDisconnectedRegular, PowerRegular, SaveRegular, SearchRegular, SettingsRegular, StopRegular, TextGrammarErrorRegular, WandRegular, WarningRegular, WifiSettingsRegular, WindowConsoleRegular } from '@fluentui/react-icons';

const STYLE = {};

@@ -26,6 +26,8 @@ export function register() {
            Folder: <FolderRegular style={STYLE} />,
            FullScreenMaximize: <FullScreenMaximizeRegular style={STYLE} />,
            Info: <InfoRegular style={STYLE} />,
            Lightbulb: <LightbulbRegular style={STYLE} />,
            LightbulbFilament: <LightbulbFilamentRegular style={STYLE} />,
            Navigation: <NavigationRegular style={STYLE} />,
            Orientation: <OrientationRegular style={STYLE} />,
            PanelBottom: <PanelBottomRegular style={STYLE} />,
@@ -64,7 +66,7 @@ export function register() {
    });
}

export default {
const Icons = {
    AddCircle: 'AddCircle',
    ArrowClockwise: 'ArrowClockwise',
    Bookmark: 'Bookmark',
@@ -83,6 +85,8 @@ export default {
    Document: 'Document',
    Folder: 'Folder',
    FullScreenMaximize: 'FullScreenMaximize',
    Lightbulb: 'Lightbulb',
    LightbulbFilament: 'LightbulbFilament',
    Info: 'Info',
    Navigation: 'Navigation',
    Orientation: 'Orientation',
@@ -106,3 +110,5 @@ export default {
    WindowConsole: 'WindowConsole',
    Document20: 'Document20'
};

export default Icons;
+1 −0
Original line number Diff line number Diff line
@@ -3,4 +3,5 @@ export * from './inject-text.js';
export * from './inject-touch.js';
export * from './rotate-device.js';
export * from './serializer.js';
export * from './set-screen-power-mode.js';
export * from './type.js';
+8 −0
Original line number Diff line number Diff line
@@ -5,6 +5,7 @@ import { AndroidKeyEventAction, ScrcpyInjectKeyCodeControlMessage } from './inje
import { ScrcpyInjectTextControlMessage } from './inject-text.js';
import { ScrcpyInjectTouchControlMessage } from './inject-touch.js';
import { ScrcpyRotateDeviceControlMessage } from './rotate-device.js';
import { AndroidScreenPowerMode, ScrcpySetScreenPowerModeControlMessage } from './set-screen-power-mode.js';
import { ScrcpyControlMessageType } from './type.js';

export class ScrcpyControlMessageSerializer {
@@ -66,6 +67,13 @@ export class ScrcpyControlMessageSerializer {
        }
    }

    public setScreenPowerMode(mode: AndroidScreenPowerMode) {
        return this.writer.write(ScrcpySetScreenPowerModeControlMessage.serialize({
            mode,
            type: this.getTypeValue(ScrcpyControlMessageType.SetScreenPowerMode),
        }));
    }

    public rotateDevice() {
        return this.writer.write(ScrcpyRotateDeviceControlMessage.serialize({
            type: this.getTypeValue(ScrcpyControlMessageType.RotateDevice),
+17 −0
Original line number Diff line number Diff line
import Struct, { placeholder } from '@yume-chan/struct';

import { ScrcpyControlMessageType } from './type.js';

// https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/view/SurfaceControl.java;l=659;drc=20303e05bf73796124ab70a279cf849b61b97905
export enum AndroidScreenPowerMode {
    Off = 0,
    Normal = 2
}

export const ScrcpySetScreenPowerModeControlMessage =
    new Struct()
        .uint8('type', ScrcpyControlMessageType.SetScreenPowerMode as const)
        .uint8('mode', placeholder<AndroidScreenPowerMode>());

export type ScrcpySetScreenPowerModeControlMessage =
    typeof ScrcpySetScreenPowerModeControlMessage['TInit'];
Loading