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

Commit 225e369f authored by Simon Chan's avatar Simon Chan
Browse files

feat(bin): add cmd wrapper

parent 9c5d2d8a
Loading
Loading
Loading
Loading
+27 −21
Original line number Diff line number Diff line
@@ -67,13 +67,15 @@ export class ScrcpyPageState {

    async pushServer() {
        const serverBuffer = await fetchServer();

        await new ReadableStream<Uint8Array>({
        await AdbScrcpyClient.pushServer(
            GLOBAL_STATE.device!,
            new ReadableStream<Uint8Array>({
                start(controller) {
                    controller.enqueue(serverBuffer);
                    controller.close();
                },
        }).pipeTo(AdbScrcpyClient.pushServer(GLOBAL_STATE.device!));
            })
        );
    }

    decoder: H264Decoder | undefined = undefined;
@@ -186,12 +188,16 @@ export class ScrcpyPageState {
            );

            try {
                await new ReadableStream<Uint8Array>({
                await AdbScrcpyClient.pushServer(
                    GLOBAL_STATE.device!,
                    new ReadableStream<Uint8Array>({
                        start(controller) {
                            controller.enqueue(serverBuffer);
                            controller.close();
                        },
                    })
                        // In fact `pushServer` will pipe the stream through a ChunkStream,
                        // but without this pipeThrough, the progress will not be updated.
                        .pipeThrough(new ChunkStream(ADB_SYNC_MAX_PACKET_SIZE))
                        .pipeThrough(
                            new ProgressStream(
@@ -200,7 +206,7 @@ export class ScrcpyPageState {
                                })
                            )
                        )
                    .pipeTo(AdbScrcpyClient.pushServer(GLOBAL_STATE.device!));
                );

                runInAction(() => {
                    this.serverUploadSpeed =
+33 −23
Original line number Diff line number Diff line
@@ -2,21 +2,22 @@

import { AdbSubprocessProtocol, encodeUtf8 } from "@yume-chan/adb";
import { AutoDisposable } from "@yume-chan/event";
import { AbortController, WritableStream } from '@yume-chan/stream-extra';
import { Terminal } from 'xterm';
import { FitAddon } from 'xterm-addon-fit';
import { SearchAddon } from 'xterm-addon-search';
import { WebglAddon } from 'xterm-addon-webgl';
import { AbortController, WritableStream } from "@yume-chan/stream-extra";
import { Terminal } from "xterm";
import { FitAddon } from "xterm-addon-fit";
import { SearchAddon } from "xterm-addon-search";
import { WebglAddon } from "xterm-addon-webgl";

export class AdbTerminal extends AutoDisposable {
    private element = document.createElement('div');
    private element = document.createElement("div");

    public terminal: Terminal = new Terminal({
        allowProposedApi: true,
        allowTransparency: true,
        cursorStyle: 'bar',
        cursorStyle: "bar",
        cursorBlink: true,
        fontFamily: '"Cascadia Code", Consolas, monospace, "Source Han Sans SC", "Microsoft YaHei"',
        fontFamily:
            '"Cascadia Code", Consolas, monospace, "Source Han Sans SC", "Microsoft YaHei"',
        letterSpacing: 1,
        scrollback: 9000,
        smoothScrollDuration: 50,
@@ -29,7 +30,9 @@ export class AdbTerminal extends AutoDisposable {

    private _socket: AdbSubprocessProtocol | undefined;
    private _socketAbortController: AbortController | undefined;
    public get socket() { return this._socket; }
    public get socket() {
        return this._socket;
    }
    public set socket(value) {
        if (this._socket) {
            // Remove event listeners
@@ -46,19 +49,26 @@ export class AdbTerminal extends AutoDisposable {
            this._socketAbortController = new AbortController();

            // pty mode only has one stream
            value.stdout.pipeTo(new WritableStream<Uint8Array>({
            value.stdout
                .pipeTo(
                    new WritableStream<Uint8Array>({
                        write: (chunk) => {
                            this.terminal.write(chunk);
                        },
            }), {
                    }),
                    {
                        signal: this._socketAbortController.signal,
            });
                    }
                )
                .catch(() => {});

            const _writer = value.stdin.getWriter();
            this.addDisposable(this.terminal.onData(data => {
            this.addDisposable(
                this.terminal.onData((data) => {
                    const buffer = encodeUtf8(data);
                    _writer.write(buffer);
            }));
                })
            );

            this.fit();
        }
@@ -67,9 +77,9 @@ export class AdbTerminal extends AutoDisposable {
    public constructor() {
        super();

        this.element.style.width = '100%';
        this.element.style.height = '100%';
        this.element.style.overflow = 'hidden';
        this.element.style.width = "100%";
        this.element.style.height = "100%";
        this.element.style.overflow = "hidden";

        this.terminal.loadAddon(this.searchAddon);
        this.terminal.loadAddon(this.fitAddon);
+13 −10
Original line number Diff line number Diff line
@@ -5,7 +5,7 @@ import {
    Stack,
    TooltipHost,
} from "@fluentui/react";
import { AdbFeatures } from "@yume-chan/adb";
import { AdbFeature } from "@yume-chan/adb";
import { observer } from "mobx-react-lite";
import type { NextPage } from "next";
import Head from "next/head";
@@ -14,25 +14,28 @@ import { GLOBAL_STATE } from "../state";
import { Icons, RouteStackProps } from "../utils";

const KNOWN_FEATURES: Record<string, string> = {
    [AdbFeatures.ShellV2]: `"shell" command now supports separating child process's stdout and stderr, and returning exit code`,
    [AdbFeature.ShellV2]: `"shell" command now supports separating child process's stdout and stderr, and returning exit code`,
    // 'cmd': '',
    [AdbFeatures.StatV2]:
    [AdbFeature.StatV2]:
        '"sync" command now supports "STA2" (returns more information of a file than old "STAT") and "LST2" (returns information of a directory) sub command',
    [AdbFeatures.ListV2]:
    [AdbFeature.ListV2]:
        '"sync" command now supports "LST2" sub command which returns more information when listing a directory than old "LIST"',
    [AdbFeatures.FixedPushMkdir]:
    [AdbFeature.FixedPushMkdir]:
        "Android 9 (P) introduced a bug that pushing files to a non-existing directory would fail. This feature indicates it's fixed (Android 10)",
    // 'apex': '',
    // 'abb': '',
    // 'fixed_push_symlink_timestamp': '',
    abb_exec:
        'Support "exec" command which can stream stdin into child process',
    [AdbFeature.AbbExec]:
        'Supports "abb_exec" variant that can be used to install App faster',
    // 'remount_shell': '',
    // 'track_app': '',
    // 'sendrecv_v2': '',
    // 'sendrecv_v2_brotli': '',
    // 'sendrecv_v2_lz4': '',
    // 'sendrecv_v2_zstd': '',
    sendrecv_v2_brotli:
        'Supports "brotli" compression algorithm when pushing/pulling files',
    sendrecv_v2_lz4:
        'Supports "lz4" compression algorithm when pushing/pulling files',
    sendrecv_v2_zstd:
        'Supports "zstd" compression algorithm when pushing/pulling files',
    // 'sendrecv_v2_dry_run_send': '',
};

+12 −20
Original line number Diff line number Diff line
@@ -29,13 +29,7 @@ import {
} from "@fluentui/react-file-type-icons";
import { useConst } from "@fluentui/react-hooks";
import { getIcon } from "@fluentui/style-utilities";
import {
    ADB_SYNC_MAX_PACKET_SIZE,
    AdbFeatures,
    LinuxFileType,
    type AdbSyncEntry,
} from "@yume-chan/adb";
import { ChunkStream } from "@yume-chan/stream-extra";
import { AdbFeature, LinuxFileType, type AdbSyncEntry } from "@yume-chan/adb";
import {
    action,
    autorun,
@@ -270,6 +264,7 @@ class FileManagerState {
                const bSortKey = b[this.sortKey]!;

                if (aSortKey === bSortKey) {
                    // use name as tie breaker
                    result = compareCaseInsensitively(a.name!, b.name!);
                } else if (typeof aSortKey === "string") {
                    result = compareCaseInsensitively(
@@ -277,7 +272,8 @@ class FileManagerState {
                        bSortKey as string
                    );
                } else {
                    result = aSortKey < bSortKey ? -1 : 1;
                    result =
                        (aSortKey as number) < (bSortKey as number) ? -1 : 1;
                }
            }

@@ -391,7 +387,7 @@ class FileManagerState {
            },
        ];

        if (GLOBAL_STATE.device?.features?.includes(AdbFeatures.ListV2)) {
        if (GLOBAL_STATE.device?.supportsFeature(AdbFeature.ListV2)) {
            list.push(
                {
                    key: "ctime",
@@ -574,21 +570,17 @@ class FileManagerState {
            );

            try {
                await createFileStream(file)
                    .pipeThrough(new ChunkStream(ADB_SYNC_MAX_PACKET_SIZE))
                    .pipeThrough(
                await sync.write(
                    itemPath,
                    createFileStream(file).pipeThrough(
                        new ProgressStream(
                            action((uploaded) => {
                                this.uploadedSize = uploaded;
                            })
                        )
                    )
                    .pipeTo(
                        sync.write(
                            itemPath,
                    ),
                    (LinuxFileType.File << 12) | 0o666,
                    file.lastModified / 1000
                        )
                );

                runInAction(() => {
+61 −9
Original line number Diff line number Diff line
import { DefaultButton, ProgressIndicator, Stack } from "@fluentui/react";
import { ADB_SYNC_MAX_PACKET_SIZE } from "@yume-chan/adb";
import { ChunkStream } from "@yume-chan/stream-extra";
import {
    Checkbox,
    PrimaryButton,
    ProgressIndicator,
    Stack,
} from "@fluentui/react";
import {
    PackageManager,
    PackageManagerInstallOptions,
} from "@yume-chan/android-bin";
import { WritableStream } from "@yume-chan/stream-extra";
import { action, makeAutoObservable, observable, runInAction } from "mobx";
import { observer } from "mobx-react-lite";
import { NextPage } from "next";
@@ -38,10 +46,17 @@ class InstallPageState {

    progress: Progress | undefined = undefined;

    log: string = "";

    options: Partial<PackageManagerInstallOptions> = {
        bypassLowTargetSdkBlock: false,
    };

    constructor() {
        makeAutoObservable(this, {
            progress: observable.ref,
            install: false,
            options: observable.deep,
        });
    }

@@ -60,11 +75,14 @@ class InstallPageState {
                totalSize: file.size,
                value: 0,
            };
            this.log = "";
        });

        await createFileStream(file)
            .pipeThrough(new ChunkStream(ADB_SYNC_MAX_PACKET_SIZE))
            .pipeThrough(
        const pm = new PackageManager(GLOBAL_STATE.device!);
        const start = Date.now();
        const log = await pm.installStream(
            file.size,
            createFileStream(file).pipeThrough(
                new ProgressStream(
                    action((uploaded) => {
                        if (uploaded !== file.size) {
@@ -87,7 +105,24 @@ class InstallPageState {
                    })
                )
            )
            .pipeTo(GLOBAL_STATE.device!.install());
        );

        const elapsed = Date.now() - start;
        await log.pipeTo(
            new WritableStream({
                write: action((chunk) => {
                    this.log += chunk;
                }),
            })
        );

        const transferRate = (
            file.size /
            (elapsed / 1000) /
            1024 /
            1024
        ).toFixed(2);
        this.log += `Install finished in ${elapsed}ms at ${transferRate}MB/s`;

        runInAction(() => {
            this.progress = {
@@ -112,9 +147,24 @@ const Install: NextPage = () => {
            </Head>

            <Stack horizontal>
                <DefaultButton
                <Checkbox
                    label="--bypass-low-target-sdk-block (Android 14)"
                    checked={state.options.bypassLowTargetSdkBlock}
                    onChange={(_, checked) => {
                        if (checked === undefined) {
                            return;
                        }
                        runInAction(() => {
                            state.options.bypassLowTargetSdkBlock = checked;
                        });
                    }}
                />
            </Stack>

            <Stack horizontal>
                <PrimaryButton
                    disabled={!GLOBAL_STATE.device || state.installing}
                    text="Open"
                    text="Browse APK"
                    onClick={state.install}
                />
            </Stack>
@@ -127,6 +177,8 @@ const Install: NextPage = () => {
                    description={Stage[state.progress.stage]}
                />
            )}

            {state.log && <pre>{state.log}</pre>}
        </Stack>
    );
};
Loading