mirror of
https://github.com/yume-chan/ya-webadb.git
synced 2025-10-03 01:39:21 +02:00
feat(adb): rewrite process spawner API (#739)
This commit is contained in:
parent
46e78401a4
commit
d3019ce738
75 changed files with 1422 additions and 1022 deletions
|
@ -135,19 +135,19 @@ createDeviceCommand("shell [args...]")
|
||||||
const ref = new Ref();
|
const ref = new Ref();
|
||||||
|
|
||||||
const adb = await createAdb(options);
|
const adb = await createAdb(options);
|
||||||
const shell = await adb.subprocess.shell(args);
|
const shell = await adb.subprocess.noneProtocol.pty(args);
|
||||||
|
|
||||||
const stdinWriter = shell.stdin.getWriter();
|
const inputWriter = shell.input.getWriter();
|
||||||
|
|
||||||
process.stdin.setRawMode(true);
|
process.stdin.setRawMode(true);
|
||||||
process.stdin.on("data", (data: Uint8Array) => {
|
process.stdin.on("data", (data: Uint8Array) => {
|
||||||
stdinWriter.write(data).catch((e) => {
|
inputWriter.write(data).catch((e) => {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
shell.stdout
|
shell.output
|
||||||
.pipeTo(
|
.pipeTo(
|
||||||
new WritableStream({
|
new WritableStream({
|
||||||
write(chunk) {
|
write(chunk) {
|
||||||
|
@ -160,11 +160,11 @@ createDeviceCommand("shell [args...]")
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
shell.exit.then(
|
shell.exited.then(
|
||||||
(code) => {
|
() => {
|
||||||
// `process.stdin.on("data")` will keep the process alive,
|
// `process.stdin.on("data")` will keep the process alive,
|
||||||
// so call `process.exit` explicitly.
|
// so call `process.exit` explicitly.
|
||||||
process.exit(code);
|
process.exit(0);
|
||||||
},
|
},
|
||||||
(e) => {
|
(e) => {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
|
@ -181,12 +181,15 @@ createDeviceCommand("logcat [args...]")
|
||||||
.configureHelp({ showGlobalOptions: true })
|
.configureHelp({ showGlobalOptions: true })
|
||||||
.action(async (args: string[], options: DeviceCommandOptions) => {
|
.action(async (args: string[], options: DeviceCommandOptions) => {
|
||||||
const adb = await createAdb(options);
|
const adb = await createAdb(options);
|
||||||
const logcat = await adb.subprocess.spawn(`logcat ${args.join(" ")}`);
|
const logcat = await adb.subprocess.noneProtocol.spawn([
|
||||||
|
"logcat",
|
||||||
|
...args,
|
||||||
|
]);
|
||||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||||
process.on("SIGINT", async () => {
|
process.on("SIGINT", async () => {
|
||||||
await logcat.kill();
|
await logcat.kill();
|
||||||
});
|
});
|
||||||
await logcat.stdout.pipeTo(
|
await logcat.output.pipeTo(
|
||||||
new WritableStream({
|
new WritableStream({
|
||||||
write: (chunk) => {
|
write: (chunk) => {
|
||||||
process.stdout.write(chunk);
|
process.stdout.write(chunk);
|
||||||
|
|
|
@ -2,14 +2,21 @@ import type { Adb } from "@yume-chan/adb";
|
||||||
import type { ScrcpyDisplay, ScrcpyEncoder } from "@yume-chan/scrcpy";
|
import type { ScrcpyDisplay, ScrcpyEncoder } from "@yume-chan/scrcpy";
|
||||||
import { ScrcpyOptions1_15 } from "@yume-chan/scrcpy";
|
import { ScrcpyOptions1_15 } from "@yume-chan/scrcpy";
|
||||||
|
|
||||||
|
import type { AdbScrcpyClientOptions } from "../client-options.js";
|
||||||
import type { AdbScrcpyConnection } from "../connection.js";
|
import type { AdbScrcpyConnection } from "../connection.js";
|
||||||
import { AdbScrcpyOptions } from "../types.js";
|
import { AdbScrcpyOptions } from "../types.js";
|
||||||
|
|
||||||
import { createConnection, getDisplays, getEncoders } from "./impl/index.js";
|
import { createConnection, getDisplays, getEncoders } from "./impl/index.js";
|
||||||
|
|
||||||
export class AdbScrcpyOptions1_15 extends AdbScrcpyOptions<ScrcpyOptions1_15.Init> {
|
export class AdbScrcpyOptions1_15 extends AdbScrcpyOptions<ScrcpyOptions1_15.Init> {
|
||||||
constructor(init: ScrcpyOptions1_15.Init, version?: string) {
|
constructor(
|
||||||
super(new ScrcpyOptions1_15(init, version));
|
init: ScrcpyOptions1_15.Init,
|
||||||
|
clientOptions?: AdbScrcpyClientOptions,
|
||||||
|
) {
|
||||||
|
super(
|
||||||
|
new ScrcpyOptions1_15(init, clientOptions?.version),
|
||||||
|
clientOptions?.spawner,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
override getEncoders(adb: Adb, path: string): Promise<ScrcpyEncoder[]> {
|
override getEncoders(adb: Adb, path: string): Promise<ScrcpyEncoder[]> {
|
||||||
|
|
|
@ -7,12 +7,19 @@ import {
|
||||||
getDisplays,
|
getDisplays,
|
||||||
getEncoders,
|
getEncoders,
|
||||||
} from "./1_15/impl/index.js";
|
} from "./1_15/impl/index.js";
|
||||||
|
import type { AdbScrcpyClientOptions } from "./client-options.js";
|
||||||
import type { AdbScrcpyConnection } from "./connection.js";
|
import type { AdbScrcpyConnection } from "./connection.js";
|
||||||
import { AdbScrcpyOptions } from "./types.js";
|
import { AdbScrcpyOptions } from "./types.js";
|
||||||
|
|
||||||
export class AdbScrcpyOptions1_15_1 extends AdbScrcpyOptions<ScrcpyOptions1_15_1.Init> {
|
export class AdbScrcpyOptions1_15_1 extends AdbScrcpyOptions<ScrcpyOptions1_15_1.Init> {
|
||||||
constructor(init: ScrcpyOptions1_15_1.Init, version?: string) {
|
constructor(
|
||||||
super(new ScrcpyOptions1_15_1(init, version));
|
init: ScrcpyOptions1_15_1.Init,
|
||||||
|
clientOptions?: AdbScrcpyClientOptions,
|
||||||
|
) {
|
||||||
|
super(
|
||||||
|
new ScrcpyOptions1_15_1(init, clientOptions?.version),
|
||||||
|
clientOptions?.spawner,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
override getEncoders(adb: Adb, path: string): Promise<ScrcpyEncoder[]> {
|
override getEncoders(adb: Adb, path: string): Promise<ScrcpyEncoder[]> {
|
||||||
|
|
|
@ -7,12 +7,19 @@ import {
|
||||||
getDisplays,
|
getDisplays,
|
||||||
getEncoders,
|
getEncoders,
|
||||||
} from "./1_15/impl/index.js";
|
} from "./1_15/impl/index.js";
|
||||||
|
import type { AdbScrcpyClientOptions } from "./client-options.js";
|
||||||
import type { AdbScrcpyConnection } from "./connection.js";
|
import type { AdbScrcpyConnection } from "./connection.js";
|
||||||
import { AdbScrcpyOptions } from "./types.js";
|
import { AdbScrcpyOptions } from "./types.js";
|
||||||
|
|
||||||
export class AdbScrcpyOptions1_16 extends AdbScrcpyOptions<ScrcpyOptions1_16.Init> {
|
export class AdbScrcpyOptions1_16 extends AdbScrcpyOptions<ScrcpyOptions1_16.Init> {
|
||||||
constructor(init: ScrcpyOptions1_16.Init, version?: string) {
|
constructor(
|
||||||
super(new ScrcpyOptions1_16(init, version));
|
init: ScrcpyOptions1_16.Init,
|
||||||
|
clientOptions?: AdbScrcpyClientOptions,
|
||||||
|
) {
|
||||||
|
super(
|
||||||
|
new ScrcpyOptions1_16(init, clientOptions?.version),
|
||||||
|
clientOptions?.spawner,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
override getEncoders(adb: Adb, path: string): Promise<ScrcpyEncoder[]> {
|
override getEncoders(adb: Adb, path: string): Promise<ScrcpyEncoder[]> {
|
||||||
|
|
|
@ -7,12 +7,19 @@ import {
|
||||||
getDisplays,
|
getDisplays,
|
||||||
getEncoders,
|
getEncoders,
|
||||||
} from "./1_15/impl/index.js";
|
} from "./1_15/impl/index.js";
|
||||||
|
import type { AdbScrcpyClientOptions } from "./client-options.js";
|
||||||
import type { AdbScrcpyConnection } from "./connection.js";
|
import type { AdbScrcpyConnection } from "./connection.js";
|
||||||
import { AdbScrcpyOptions } from "./types.js";
|
import { AdbScrcpyOptions } from "./types.js";
|
||||||
|
|
||||||
export class AdbScrcpyOptions1_17 extends AdbScrcpyOptions<ScrcpyOptions1_17.Init> {
|
export class AdbScrcpyOptions1_17 extends AdbScrcpyOptions<ScrcpyOptions1_17.Init> {
|
||||||
constructor(init: ScrcpyOptions1_17.Init, version?: string) {
|
constructor(
|
||||||
super(new ScrcpyOptions1_17(init, version));
|
init: ScrcpyOptions1_17.Init,
|
||||||
|
clientOptions?: AdbScrcpyClientOptions,
|
||||||
|
) {
|
||||||
|
super(
|
||||||
|
new ScrcpyOptions1_17(init, clientOptions?.version),
|
||||||
|
clientOptions?.spawner,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
override getEncoders(adb: Adb, path: string): Promise<ScrcpyEncoder[]> {
|
override getEncoders(adb: Adb, path: string): Promise<ScrcpyEncoder[]> {
|
||||||
|
|
|
@ -7,12 +7,19 @@ import {
|
||||||
getDisplays,
|
getDisplays,
|
||||||
getEncoders,
|
getEncoders,
|
||||||
} from "./1_15/impl/index.js";
|
} from "./1_15/impl/index.js";
|
||||||
|
import type { AdbScrcpyClientOptions } from "./client-options.js";
|
||||||
import type { AdbScrcpyConnection } from "./connection.js";
|
import type { AdbScrcpyConnection } from "./connection.js";
|
||||||
import { AdbScrcpyOptions } from "./types.js";
|
import { AdbScrcpyOptions } from "./types.js";
|
||||||
|
|
||||||
export class AdbScrcpyOptions1_18 extends AdbScrcpyOptions<ScrcpyOptions1_18.Init> {
|
export class AdbScrcpyOptions1_18 extends AdbScrcpyOptions<ScrcpyOptions1_18.Init> {
|
||||||
constructor(init: ScrcpyOptions1_18.Init, version?: string) {
|
constructor(
|
||||||
super(new ScrcpyOptions1_18(init, version));
|
init: ScrcpyOptions1_18.Init,
|
||||||
|
clientOptions?: AdbScrcpyClientOptions,
|
||||||
|
) {
|
||||||
|
super(
|
||||||
|
new ScrcpyOptions1_18(init, clientOptions?.version),
|
||||||
|
clientOptions?.spawner,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
override getEncoders(adb: Adb, path: string): Promise<ScrcpyEncoder[]> {
|
override getEncoders(adb: Adb, path: string): Promise<ScrcpyEncoder[]> {
|
||||||
|
|
|
@ -7,12 +7,19 @@ import {
|
||||||
getDisplays,
|
getDisplays,
|
||||||
getEncoders,
|
getEncoders,
|
||||||
} from "./1_15/impl/index.js";
|
} from "./1_15/impl/index.js";
|
||||||
|
import type { AdbScrcpyClientOptions } from "./client-options.js";
|
||||||
import type { AdbScrcpyConnection } from "./connection.js";
|
import type { AdbScrcpyConnection } from "./connection.js";
|
||||||
import { AdbScrcpyOptions } from "./types.js";
|
import { AdbScrcpyOptions } from "./types.js";
|
||||||
|
|
||||||
export class AdbScrcpyOptions1_19 extends AdbScrcpyOptions<ScrcpyOptions1_19.Init> {
|
export class AdbScrcpyOptions1_19 extends AdbScrcpyOptions<ScrcpyOptions1_19.Init> {
|
||||||
constructor(init: ScrcpyOptions1_19.Init, version?: string) {
|
constructor(
|
||||||
super(new ScrcpyOptions1_19(init, version));
|
init: ScrcpyOptions1_19.Init,
|
||||||
|
clientOptions?: AdbScrcpyClientOptions,
|
||||||
|
) {
|
||||||
|
super(
|
||||||
|
new ScrcpyOptions1_19(init, clientOptions?.version),
|
||||||
|
clientOptions?.spawner,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
override getEncoders(adb: Adb, path: string): Promise<ScrcpyEncoder[]> {
|
override getEncoders(adb: Adb, path: string): Promise<ScrcpyEncoder[]> {
|
||||||
|
|
|
@ -7,12 +7,19 @@ import {
|
||||||
getDisplays,
|
getDisplays,
|
||||||
getEncoders,
|
getEncoders,
|
||||||
} from "./1_15/impl/index.js";
|
} from "./1_15/impl/index.js";
|
||||||
|
import type { AdbScrcpyClientOptions } from "./client-options.js";
|
||||||
import type { AdbScrcpyConnection } from "./connection.js";
|
import type { AdbScrcpyConnection } from "./connection.js";
|
||||||
import { AdbScrcpyOptions } from "./types.js";
|
import { AdbScrcpyOptions } from "./types.js";
|
||||||
|
|
||||||
export class AdbScrcpyOptions1_20 extends AdbScrcpyOptions<ScrcpyOptions1_20.Init> {
|
export class AdbScrcpyOptions1_20 extends AdbScrcpyOptions<ScrcpyOptions1_20.Init> {
|
||||||
constructor(init: ScrcpyOptions1_20.Init, version?: string) {
|
constructor(
|
||||||
super(new ScrcpyOptions1_20(init, version));
|
init: ScrcpyOptions1_20.Init,
|
||||||
|
clientOptions?: AdbScrcpyClientOptions,
|
||||||
|
) {
|
||||||
|
super(
|
||||||
|
new ScrcpyOptions1_20(init, clientOptions?.version),
|
||||||
|
clientOptions?.spawner,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
override getEncoders(adb: Adb, path: string): Promise<ScrcpyEncoder[]> {
|
override getEncoders(adb: Adb, path: string): Promise<ScrcpyEncoder[]> {
|
||||||
|
|
|
@ -7,12 +7,19 @@ import {
|
||||||
getDisplays,
|
getDisplays,
|
||||||
getEncoders,
|
getEncoders,
|
||||||
} from "./1_15/impl/index.js";
|
} from "./1_15/impl/index.js";
|
||||||
|
import type { AdbScrcpyClientOptions } from "./client-options.js";
|
||||||
import type { AdbScrcpyConnection } from "./connection.js";
|
import type { AdbScrcpyConnection } from "./connection.js";
|
||||||
import { AdbScrcpyOptions } from "./types.js";
|
import { AdbScrcpyOptions } from "./types.js";
|
||||||
|
|
||||||
export class AdbScrcpyOptions1_21 extends AdbScrcpyOptions<ScrcpyOptions1_21.Init> {
|
export class AdbScrcpyOptions1_21 extends AdbScrcpyOptions<ScrcpyOptions1_21.Init> {
|
||||||
constructor(init: ScrcpyOptions1_21.Init, version?: string) {
|
constructor(
|
||||||
super(new ScrcpyOptions1_21(init, version));
|
init: ScrcpyOptions1_21.Init,
|
||||||
|
clientOptions?: AdbScrcpyClientOptions,
|
||||||
|
) {
|
||||||
|
super(
|
||||||
|
new ScrcpyOptions1_21(init, clientOptions?.version),
|
||||||
|
clientOptions?.spawner,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
override getEncoders(adb: Adb, path: string): Promise<ScrcpyEncoder[]> {
|
override getEncoders(adb: Adb, path: string): Promise<ScrcpyEncoder[]> {
|
||||||
|
|
|
@ -7,12 +7,19 @@ import {
|
||||||
getDisplays,
|
getDisplays,
|
||||||
getEncoders,
|
getEncoders,
|
||||||
} from "../1_15/impl/index.js";
|
} from "../1_15/impl/index.js";
|
||||||
|
import type { AdbScrcpyClientOptions } from "../client-options.js";
|
||||||
import type { AdbScrcpyConnection } from "../connection.js";
|
import type { AdbScrcpyConnection } from "../connection.js";
|
||||||
import { AdbScrcpyOptions } from "../types.js";
|
import { AdbScrcpyOptions } from "../types.js";
|
||||||
|
|
||||||
export class AdbScrcpyOptions1_22 extends AdbScrcpyOptions<ScrcpyOptions1_22.Init> {
|
export class AdbScrcpyOptions1_22 extends AdbScrcpyOptions<ScrcpyOptions1_22.Init> {
|
||||||
constructor(init: ScrcpyOptions1_22.Init, version?: string) {
|
constructor(
|
||||||
super(new ScrcpyOptions1_22(init, version));
|
init: ScrcpyOptions1_22.Init,
|
||||||
|
clientOptions?: AdbScrcpyClientOptions,
|
||||||
|
) {
|
||||||
|
super(
|
||||||
|
new ScrcpyOptions1_22(init, clientOptions?.version),
|
||||||
|
clientOptions?.spawner,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
override getEncoders(adb: Adb, path: string): Promise<ScrcpyEncoder[]> {
|
override getEncoders(adb: Adb, path: string): Promise<ScrcpyEncoder[]> {
|
||||||
|
|
|
@ -7,12 +7,19 @@ import {
|
||||||
getDisplays,
|
getDisplays,
|
||||||
getEncoders,
|
getEncoders,
|
||||||
} from "./1_22/impl/index.js";
|
} from "./1_22/impl/index.js";
|
||||||
|
import type { AdbScrcpyClientOptions } from "./client-options.js";
|
||||||
import type { AdbScrcpyConnection } from "./connection.js";
|
import type { AdbScrcpyConnection } from "./connection.js";
|
||||||
import { AdbScrcpyOptions } from "./types.js";
|
import { AdbScrcpyOptions } from "./types.js";
|
||||||
|
|
||||||
export class AdbScrcpyOptions1_23 extends AdbScrcpyOptions<ScrcpyOptions1_23.Init> {
|
export class AdbScrcpyOptions1_23 extends AdbScrcpyOptions<ScrcpyOptions1_23.Init> {
|
||||||
constructor(init: ScrcpyOptions1_23.Init, version?: string) {
|
constructor(
|
||||||
super(new ScrcpyOptions1_23(init, version));
|
init: ScrcpyOptions1_23.Init,
|
||||||
|
clientOptions?: AdbScrcpyClientOptions,
|
||||||
|
) {
|
||||||
|
super(
|
||||||
|
new ScrcpyOptions1_23(init, clientOptions?.version),
|
||||||
|
clientOptions?.spawner,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
override getEncoders(adb: Adb, path: string): Promise<ScrcpyEncoder[]> {
|
override getEncoders(adb: Adb, path: string): Promise<ScrcpyEncoder[]> {
|
||||||
|
|
|
@ -7,12 +7,19 @@ import {
|
||||||
getDisplays,
|
getDisplays,
|
||||||
getEncoders,
|
getEncoders,
|
||||||
} from "./1_22/impl/index.js";
|
} from "./1_22/impl/index.js";
|
||||||
|
import type { AdbScrcpyClientOptions } from "./client-options.js";
|
||||||
import type { AdbScrcpyConnection } from "./connection.js";
|
import type { AdbScrcpyConnection } from "./connection.js";
|
||||||
import { AdbScrcpyOptions } from "./types.js";
|
import { AdbScrcpyOptions } from "./types.js";
|
||||||
|
|
||||||
export class AdbScrcpyOptions1_24 extends AdbScrcpyOptions<ScrcpyOptions1_24.Init> {
|
export class AdbScrcpyOptions1_24 extends AdbScrcpyOptions<ScrcpyOptions1_24.Init> {
|
||||||
constructor(init: ScrcpyOptions1_24.Init, version?: string) {
|
constructor(
|
||||||
super(new ScrcpyOptions1_24(init, version));
|
init: ScrcpyOptions1_24.Init,
|
||||||
|
clientOptions?: AdbScrcpyClientOptions,
|
||||||
|
) {
|
||||||
|
super(
|
||||||
|
new ScrcpyOptions1_24(init, clientOptions?.version),
|
||||||
|
clientOptions?.spawner,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
override getEncoders(adb: Adb, path: string): Promise<ScrcpyEncoder[]> {
|
override getEncoders(adb: Adb, path: string): Promise<ScrcpyEncoder[]> {
|
||||||
|
|
|
@ -7,12 +7,19 @@ import {
|
||||||
getDisplays,
|
getDisplays,
|
||||||
getEncoders,
|
getEncoders,
|
||||||
} from "./1_22/impl/index.js";
|
} from "./1_22/impl/index.js";
|
||||||
|
import type { AdbScrcpyClientOptions } from "./client-options.js";
|
||||||
import type { AdbScrcpyConnection } from "./connection.js";
|
import type { AdbScrcpyConnection } from "./connection.js";
|
||||||
import { AdbScrcpyOptions } from "./types.js";
|
import { AdbScrcpyOptions } from "./types.js";
|
||||||
|
|
||||||
export class AdbScrcpyOptions1_25 extends AdbScrcpyOptions<ScrcpyOptions1_25.Init> {
|
export class AdbScrcpyOptions1_25 extends AdbScrcpyOptions<ScrcpyOptions1_25.Init> {
|
||||||
constructor(init: ScrcpyOptions1_25.Init, version?: string) {
|
constructor(
|
||||||
super(new ScrcpyOptions1_25(init, version));
|
init: ScrcpyOptions1_25.Init,
|
||||||
|
clientOptions?: AdbScrcpyClientOptions,
|
||||||
|
) {
|
||||||
|
super(
|
||||||
|
new ScrcpyOptions1_25(init, clientOptions?.version),
|
||||||
|
clientOptions?.spawner,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
override getEncoders(adb: Adb, path: string): Promise<ScrcpyEncoder[]> {
|
override getEncoders(adb: Adb, path: string): Promise<ScrcpyEncoder[]> {
|
||||||
|
|
|
@ -2,14 +2,21 @@ import type { Adb } from "@yume-chan/adb";
|
||||||
import type { ScrcpyDisplay, ScrcpyEncoder } from "@yume-chan/scrcpy";
|
import type { ScrcpyDisplay, ScrcpyEncoder } from "@yume-chan/scrcpy";
|
||||||
import { ScrcpyOptions2_0 } from "@yume-chan/scrcpy";
|
import { ScrcpyOptions2_0 } from "@yume-chan/scrcpy";
|
||||||
|
|
||||||
|
import type { AdbScrcpyClientOptions } from "../client-options.js";
|
||||||
import type { AdbScrcpyConnection } from "../connection.js";
|
import type { AdbScrcpyConnection } from "../connection.js";
|
||||||
import { AdbScrcpyOptions } from "../types.js";
|
import { AdbScrcpyOptions } from "../types.js";
|
||||||
|
|
||||||
import { createConnection, getDisplays, getEncoders } from "./impl/index.js";
|
import { createConnection, getDisplays, getEncoders } from "./impl/index.js";
|
||||||
|
|
||||||
export class AdbScrcpyOptions2_0 extends AdbScrcpyOptions<ScrcpyOptions2_0.Init> {
|
export class AdbScrcpyOptions2_0 extends AdbScrcpyOptions<ScrcpyOptions2_0.Init> {
|
||||||
constructor(init: ScrcpyOptions2_0.Init, version?: string) {
|
constructor(
|
||||||
super(new ScrcpyOptions2_0(init, version));
|
init: ScrcpyOptions2_0.Init,
|
||||||
|
clientOptions?: AdbScrcpyClientOptions,
|
||||||
|
) {
|
||||||
|
super(
|
||||||
|
new ScrcpyOptions2_0(init, clientOptions?.version),
|
||||||
|
clientOptions?.spawner,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
override getEncoders(adb: Adb, path: string): Promise<ScrcpyEncoder[]> {
|
override getEncoders(adb: Adb, path: string): Promise<ScrcpyEncoder[]> {
|
||||||
|
|
|
@ -7,14 +7,21 @@ import {
|
||||||
getDisplays,
|
getDisplays,
|
||||||
getEncoders,
|
getEncoders,
|
||||||
} from "../2_1/impl/index.js";
|
} from "../2_1/impl/index.js";
|
||||||
|
import type { AdbScrcpyClientOptions } from "../client-options.js";
|
||||||
import type { AdbScrcpyConnection } from "../connection.js";
|
import type { AdbScrcpyConnection } from "../connection.js";
|
||||||
import { AdbScrcpyOptions } from "../types.js";
|
import { AdbScrcpyOptions } from "../types.js";
|
||||||
|
|
||||||
export class AdbScrcpyOptions2_1<
|
export class AdbScrcpyOptions2_1<
|
||||||
TVideo extends boolean,
|
TVideo extends boolean,
|
||||||
> extends AdbScrcpyOptions<ScrcpyOptions2_1.Init<TVideo>> {
|
> extends AdbScrcpyOptions<ScrcpyOptions2_1.Init<TVideo>> {
|
||||||
constructor(init: ScrcpyOptions2_1.Init<TVideo>, version?: string) {
|
constructor(
|
||||||
super(new ScrcpyOptions2_1(init, version));
|
init: ScrcpyOptions2_1.Init<TVideo>,
|
||||||
|
clientOptions?: AdbScrcpyClientOptions,
|
||||||
|
) {
|
||||||
|
super(
|
||||||
|
new ScrcpyOptions2_1(init, clientOptions?.version),
|
||||||
|
clientOptions?.spawner,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
override getEncoders(adb: Adb, path: string): Promise<ScrcpyEncoder[]> {
|
override getEncoders(adb: Adb, path: string): Promise<ScrcpyEncoder[]> {
|
||||||
|
|
|
@ -7,14 +7,21 @@ import {
|
||||||
getDisplays,
|
getDisplays,
|
||||||
getEncoders,
|
getEncoders,
|
||||||
} from "./2_1/impl/index.js";
|
} from "./2_1/impl/index.js";
|
||||||
|
import type { AdbScrcpyClientOptions } from "./client-options.js";
|
||||||
import type { AdbScrcpyConnection } from "./connection.js";
|
import type { AdbScrcpyConnection } from "./connection.js";
|
||||||
import { AdbScrcpyOptions } from "./types.js";
|
import { AdbScrcpyOptions } from "./types.js";
|
||||||
|
|
||||||
export class AdbScrcpyOptions2_1_1<
|
export class AdbScrcpyOptions2_1_1<
|
||||||
TVideo extends boolean,
|
TVideo extends boolean,
|
||||||
> extends AdbScrcpyOptions<ScrcpyOptions2_1_1.Init<TVideo>> {
|
> extends AdbScrcpyOptions<ScrcpyOptions2_1_1.Init<TVideo>> {
|
||||||
constructor(init: ScrcpyOptions2_1_1.Init<TVideo>, version?: string) {
|
constructor(
|
||||||
super(new ScrcpyOptions2_1_1(init, version));
|
init: ScrcpyOptions2_1_1.Init<TVideo>,
|
||||||
|
clientOptions?: AdbScrcpyClientOptions,
|
||||||
|
) {
|
||||||
|
super(
|
||||||
|
new ScrcpyOptions2_1_1(init, clientOptions?.version),
|
||||||
|
clientOptions?.spawner,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
override getEncoders(adb: Adb, path: string): Promise<ScrcpyEncoder[]> {
|
override getEncoders(adb: Adb, path: string): Promise<ScrcpyEncoder[]> {
|
||||||
|
|
|
@ -7,14 +7,21 @@ import {
|
||||||
getDisplays,
|
getDisplays,
|
||||||
getEncoders,
|
getEncoders,
|
||||||
} from "./2_1/impl/index.js";
|
} from "./2_1/impl/index.js";
|
||||||
|
import type { AdbScrcpyClientOptions } from "./client-options.js";
|
||||||
import type { AdbScrcpyConnection } from "./connection.js";
|
import type { AdbScrcpyConnection } from "./connection.js";
|
||||||
import { AdbScrcpyOptions } from "./types.js";
|
import { AdbScrcpyOptions } from "./types.js";
|
||||||
|
|
||||||
export class AdbScrcpyOptions2_2<
|
export class AdbScrcpyOptions2_2<
|
||||||
TVideo extends boolean,
|
TVideo extends boolean,
|
||||||
> extends AdbScrcpyOptions<ScrcpyOptions2_2.Init<TVideo>> {
|
> extends AdbScrcpyOptions<ScrcpyOptions2_2.Init<TVideo>> {
|
||||||
constructor(init: ScrcpyOptions2_2.Init<TVideo>, version?: string) {
|
constructor(
|
||||||
super(new ScrcpyOptions2_2(init, version));
|
init: ScrcpyOptions2_2.Init<TVideo>,
|
||||||
|
clientOptions?: AdbScrcpyClientOptions,
|
||||||
|
) {
|
||||||
|
super(
|
||||||
|
new ScrcpyOptions2_2(init, clientOptions?.version),
|
||||||
|
clientOptions?.spawner,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
override getEncoders(adb: Adb, path: string): Promise<ScrcpyEncoder[]> {
|
override getEncoders(adb: Adb, path: string): Promise<ScrcpyEncoder[]> {
|
||||||
|
|
|
@ -7,14 +7,21 @@ import {
|
||||||
getDisplays,
|
getDisplays,
|
||||||
getEncoders,
|
getEncoders,
|
||||||
} from "./2_1/impl/index.js";
|
} from "./2_1/impl/index.js";
|
||||||
|
import type { AdbScrcpyClientOptions } from "./client-options.js";
|
||||||
import type { AdbScrcpyConnection } from "./connection.js";
|
import type { AdbScrcpyConnection } from "./connection.js";
|
||||||
import { AdbScrcpyOptions } from "./types.js";
|
import { AdbScrcpyOptions } from "./types.js";
|
||||||
|
|
||||||
export class AdbScrcpyOptions2_3<
|
export class AdbScrcpyOptions2_3<
|
||||||
TVideo extends boolean,
|
TVideo extends boolean,
|
||||||
> extends AdbScrcpyOptions<ScrcpyOptions2_3.Init<TVideo>> {
|
> extends AdbScrcpyOptions<ScrcpyOptions2_3.Init<TVideo>> {
|
||||||
constructor(init: ScrcpyOptions2_3.Init<TVideo>, version?: string) {
|
constructor(
|
||||||
super(new ScrcpyOptions2_3(init, version));
|
init: ScrcpyOptions2_3.Init<TVideo>,
|
||||||
|
clientOptions?: AdbScrcpyClientOptions,
|
||||||
|
) {
|
||||||
|
super(
|
||||||
|
new ScrcpyOptions2_3(init, clientOptions?.version),
|
||||||
|
clientOptions?.spawner,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
override getEncoders(adb: Adb, path: string): Promise<ScrcpyEncoder[]> {
|
override getEncoders(adb: Adb, path: string): Promise<ScrcpyEncoder[]> {
|
||||||
|
|
|
@ -7,14 +7,21 @@ import {
|
||||||
getDisplays,
|
getDisplays,
|
||||||
getEncoders,
|
getEncoders,
|
||||||
} from "./2_1/impl/index.js";
|
} from "./2_1/impl/index.js";
|
||||||
|
import type { AdbScrcpyClientOptions } from "./client-options.js";
|
||||||
import type { AdbScrcpyConnection } from "./connection.js";
|
import type { AdbScrcpyConnection } from "./connection.js";
|
||||||
import { AdbScrcpyOptions } from "./types.js";
|
import { AdbScrcpyOptions } from "./types.js";
|
||||||
|
|
||||||
export class AdbScrcpyOptions2_3_1<
|
export class AdbScrcpyOptions2_3_1<
|
||||||
TVideo extends boolean,
|
TVideo extends boolean,
|
||||||
> extends AdbScrcpyOptions<ScrcpyOptions2_3_1.Init<TVideo>> {
|
> extends AdbScrcpyOptions<ScrcpyOptions2_3_1.Init<TVideo>> {
|
||||||
constructor(init: ScrcpyOptions2_3_1.Init<TVideo>, version?: string) {
|
constructor(
|
||||||
super(new ScrcpyOptions2_3_1(init, version));
|
init: ScrcpyOptions2_3_1.Init<TVideo>,
|
||||||
|
clientOptions?: AdbScrcpyClientOptions,
|
||||||
|
) {
|
||||||
|
super(
|
||||||
|
new ScrcpyOptions2_3_1(init, clientOptions?.version),
|
||||||
|
clientOptions?.spawner,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
override getEncoders(adb: Adb, path: string): Promise<ScrcpyEncoder[]> {
|
override getEncoders(adb: Adb, path: string): Promise<ScrcpyEncoder[]> {
|
||||||
|
|
|
@ -7,14 +7,21 @@ import {
|
||||||
getDisplays,
|
getDisplays,
|
||||||
getEncoders,
|
getEncoders,
|
||||||
} from "./2_1/impl/index.js";
|
} from "./2_1/impl/index.js";
|
||||||
|
import type { AdbScrcpyClientOptions } from "./client-options.js";
|
||||||
import type { AdbScrcpyConnection } from "./connection.js";
|
import type { AdbScrcpyConnection } from "./connection.js";
|
||||||
import { AdbScrcpyOptions } from "./types.js";
|
import { AdbScrcpyOptions } from "./types.js";
|
||||||
|
|
||||||
export class AdbScrcpyOptions2_4<
|
export class AdbScrcpyOptions2_4<
|
||||||
TVideo extends boolean,
|
TVideo extends boolean,
|
||||||
> extends AdbScrcpyOptions<ScrcpyOptions2_4.Init<TVideo>> {
|
> extends AdbScrcpyOptions<ScrcpyOptions2_4.Init<TVideo>> {
|
||||||
constructor(init: ScrcpyOptions2_4.Init<TVideo>, version?: string) {
|
constructor(
|
||||||
super(new ScrcpyOptions2_4(init, version));
|
init: ScrcpyOptions2_4.Init<TVideo>,
|
||||||
|
clientOptions?: AdbScrcpyClientOptions,
|
||||||
|
) {
|
||||||
|
super(
|
||||||
|
new ScrcpyOptions2_4(init, clientOptions?.version),
|
||||||
|
clientOptions?.spawner,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
override getEncoders(adb: Adb, path: string): Promise<ScrcpyEncoder[]> {
|
override getEncoders(adb: Adb, path: string): Promise<ScrcpyEncoder[]> {
|
||||||
|
|
|
@ -7,14 +7,21 @@ import {
|
||||||
getDisplays,
|
getDisplays,
|
||||||
getEncoders,
|
getEncoders,
|
||||||
} from "./2_1/impl/index.js";
|
} from "./2_1/impl/index.js";
|
||||||
|
import type { AdbScrcpyClientOptions } from "./client-options.js";
|
||||||
import type { AdbScrcpyConnection } from "./connection.js";
|
import type { AdbScrcpyConnection } from "./connection.js";
|
||||||
import { AdbScrcpyOptions } from "./types.js";
|
import { AdbScrcpyOptions } from "./types.js";
|
||||||
|
|
||||||
export class AdbScrcpyOptions2_5<
|
export class AdbScrcpyOptions2_5<
|
||||||
TVideo extends boolean,
|
TVideo extends boolean,
|
||||||
> extends AdbScrcpyOptions<ScrcpyOptions2_5.Init<TVideo>> {
|
> extends AdbScrcpyOptions<ScrcpyOptions2_5.Init<TVideo>> {
|
||||||
constructor(init: ScrcpyOptions2_5.Init<TVideo>, version?: string) {
|
constructor(
|
||||||
super(new ScrcpyOptions2_5(init, version));
|
init: ScrcpyOptions2_5.Init<TVideo>,
|
||||||
|
clientOptions?: AdbScrcpyClientOptions,
|
||||||
|
) {
|
||||||
|
super(
|
||||||
|
new ScrcpyOptions2_5(init, clientOptions?.version),
|
||||||
|
clientOptions?.spawner,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
override getEncoders(adb: Adb, path: string): Promise<ScrcpyEncoder[]> {
|
override getEncoders(adb: Adb, path: string): Promise<ScrcpyEncoder[]> {
|
||||||
|
|
|
@ -7,14 +7,21 @@ import {
|
||||||
getDisplays,
|
getDisplays,
|
||||||
getEncoders,
|
getEncoders,
|
||||||
} from "./2_1/impl/index.js";
|
} from "./2_1/impl/index.js";
|
||||||
|
import type { AdbScrcpyClientOptions } from "./client-options.js";
|
||||||
import type { AdbScrcpyConnection } from "./connection.js";
|
import type { AdbScrcpyConnection } from "./connection.js";
|
||||||
import { AdbScrcpyOptions } from "./types.js";
|
import { AdbScrcpyOptions } from "./types.js";
|
||||||
|
|
||||||
export class AdbScrcpyOptions2_6<
|
export class AdbScrcpyOptions2_6<
|
||||||
TVideo extends boolean,
|
TVideo extends boolean,
|
||||||
> extends AdbScrcpyOptions<ScrcpyOptions2_6.Init<TVideo>> {
|
> extends AdbScrcpyOptions<ScrcpyOptions2_6.Init<TVideo>> {
|
||||||
constructor(init: ScrcpyOptions2_6.Init<TVideo>, version?: string) {
|
constructor(
|
||||||
super(new ScrcpyOptions2_6(init, version));
|
init: ScrcpyOptions2_6.Init<TVideo>,
|
||||||
|
clientOptions?: AdbScrcpyClientOptions,
|
||||||
|
) {
|
||||||
|
super(
|
||||||
|
new ScrcpyOptions2_6(init, clientOptions?.version),
|
||||||
|
clientOptions?.spawner,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
override getEncoders(adb: Adb, path: string): Promise<ScrcpyEncoder[]> {
|
override getEncoders(adb: Adb, path: string): Promise<ScrcpyEncoder[]> {
|
||||||
|
|
|
@ -7,14 +7,21 @@ import {
|
||||||
getDisplays,
|
getDisplays,
|
||||||
getEncoders,
|
getEncoders,
|
||||||
} from "./2_1/impl/index.js";
|
} from "./2_1/impl/index.js";
|
||||||
|
import type { AdbScrcpyClientOptions } from "./client-options.js";
|
||||||
import type { AdbScrcpyConnection } from "./connection.js";
|
import type { AdbScrcpyConnection } from "./connection.js";
|
||||||
import { AdbScrcpyOptions } from "./types.js";
|
import { AdbScrcpyOptions } from "./types.js";
|
||||||
|
|
||||||
export class AdbScrcpyOptions2_7<
|
export class AdbScrcpyOptions2_7<
|
||||||
TVideo extends boolean,
|
TVideo extends boolean,
|
||||||
> extends AdbScrcpyOptions<ScrcpyOptions2_7.Init<TVideo>> {
|
> extends AdbScrcpyOptions<ScrcpyOptions2_7.Init<TVideo>> {
|
||||||
constructor(init: ScrcpyOptions2_7.Init<TVideo>, version?: string) {
|
constructor(
|
||||||
super(new ScrcpyOptions2_7(init, version));
|
init: ScrcpyOptions2_7.Init<TVideo>,
|
||||||
|
clientOptions?: AdbScrcpyClientOptions,
|
||||||
|
) {
|
||||||
|
super(
|
||||||
|
new ScrcpyOptions2_7(init, clientOptions?.version),
|
||||||
|
clientOptions?.spawner,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
override getEncoders(adb: Adb, path: string): Promise<ScrcpyEncoder[]> {
|
override getEncoders(adb: Adb, path: string): Promise<ScrcpyEncoder[]> {
|
||||||
|
|
|
@ -7,14 +7,21 @@ import {
|
||||||
getDisplays,
|
getDisplays,
|
||||||
getEncoders,
|
getEncoders,
|
||||||
} from "./2_1/impl/index.js";
|
} from "./2_1/impl/index.js";
|
||||||
|
import type { AdbScrcpyClientOptions } from "./client-options.js";
|
||||||
import type { AdbScrcpyConnection } from "./connection.js";
|
import type { AdbScrcpyConnection } from "./connection.js";
|
||||||
import { AdbScrcpyOptions } from "./types.js";
|
import { AdbScrcpyOptions } from "./types.js";
|
||||||
|
|
||||||
export class AdbScrcpyOptions3_0<
|
export class AdbScrcpyOptions3_0<
|
||||||
TVideo extends boolean,
|
TVideo extends boolean,
|
||||||
> extends AdbScrcpyOptions<ScrcpyOptions3_0.Init<TVideo>> {
|
> extends AdbScrcpyOptions<ScrcpyOptions3_0.Init<TVideo>> {
|
||||||
constructor(init: ScrcpyOptions3_0.Init<TVideo>, version?: string) {
|
constructor(
|
||||||
super(new ScrcpyOptions3_0(init, version));
|
init: ScrcpyOptions3_0.Init<TVideo>,
|
||||||
|
clientOptions?: AdbScrcpyClientOptions,
|
||||||
|
) {
|
||||||
|
super(
|
||||||
|
new ScrcpyOptions3_0(init, clientOptions?.version),
|
||||||
|
clientOptions?.spawner,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
override getEncoders(adb: Adb, path: string): Promise<ScrcpyEncoder[]> {
|
override getEncoders(adb: Adb, path: string): Promise<ScrcpyEncoder[]> {
|
||||||
|
|
|
@ -7,14 +7,21 @@ import {
|
||||||
getDisplays,
|
getDisplays,
|
||||||
getEncoders,
|
getEncoders,
|
||||||
} from "./2_1/impl/index.js";
|
} from "./2_1/impl/index.js";
|
||||||
|
import type { AdbScrcpyClientOptions } from "./client-options.js";
|
||||||
import type { AdbScrcpyConnection } from "./connection.js";
|
import type { AdbScrcpyConnection } from "./connection.js";
|
||||||
import { AdbScrcpyOptions } from "./types.js";
|
import { AdbScrcpyOptions } from "./types.js";
|
||||||
|
|
||||||
export class AdbScrcpyOptions3_0_1<
|
export class AdbScrcpyOptions3_0_1<
|
||||||
TVideo extends boolean,
|
TVideo extends boolean,
|
||||||
> extends AdbScrcpyOptions<ScrcpyOptions3_0_1.Init<TVideo>> {
|
> extends AdbScrcpyOptions<ScrcpyOptions3_0_1.Init<TVideo>> {
|
||||||
constructor(init: ScrcpyOptions3_0_1.Init<TVideo>, version?: string) {
|
constructor(
|
||||||
super(new ScrcpyOptions3_0_1(init, version));
|
init: ScrcpyOptions3_0_1.Init<TVideo>,
|
||||||
|
clientOptions?: AdbScrcpyClientOptions,
|
||||||
|
) {
|
||||||
|
super(
|
||||||
|
new ScrcpyOptions3_0_1(init, clientOptions?.version),
|
||||||
|
clientOptions?.spawner,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
override getEncoders(adb: Adb, path: string): Promise<ScrcpyEncoder[]> {
|
override getEncoders(adb: Adb, path: string): Promise<ScrcpyEncoder[]> {
|
||||||
|
|
|
@ -7,14 +7,21 @@ import {
|
||||||
getDisplays,
|
getDisplays,
|
||||||
getEncoders,
|
getEncoders,
|
||||||
} from "./2_1/impl/index.js";
|
} from "./2_1/impl/index.js";
|
||||||
|
import type { AdbScrcpyClientOptions } from "./client-options.js";
|
||||||
import type { AdbScrcpyConnection } from "./connection.js";
|
import type { AdbScrcpyConnection } from "./connection.js";
|
||||||
import { AdbScrcpyOptions } from "./types.js";
|
import { AdbScrcpyOptions } from "./types.js";
|
||||||
|
|
||||||
export class AdbScrcpyOptions3_0_2<
|
export class AdbScrcpyOptions3_0_2<
|
||||||
TVideo extends boolean,
|
TVideo extends boolean,
|
||||||
> extends AdbScrcpyOptions<ScrcpyOptions3_0_2.Init<TVideo>> {
|
> extends AdbScrcpyOptions<ScrcpyOptions3_0_2.Init<TVideo>> {
|
||||||
constructor(init: ScrcpyOptions3_0_2.Init<TVideo>, version?: string) {
|
constructor(
|
||||||
super(new ScrcpyOptions3_0_2(init, version));
|
init: ScrcpyOptions3_0_2.Init<TVideo>,
|
||||||
|
clientOptions?: AdbScrcpyClientOptions,
|
||||||
|
) {
|
||||||
|
super(
|
||||||
|
new ScrcpyOptions3_0_2(init, clientOptions?.version),
|
||||||
|
clientOptions?.spawner,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
override getEncoders(adb: Adb, path: string): Promise<ScrcpyEncoder[]> {
|
override getEncoders(adb: Adb, path: string): Promise<ScrcpyEncoder[]> {
|
||||||
|
|
|
@ -7,14 +7,21 @@ import {
|
||||||
getDisplays,
|
getDisplays,
|
||||||
getEncoders,
|
getEncoders,
|
||||||
} from "./2_1/impl/index.js";
|
} from "./2_1/impl/index.js";
|
||||||
|
import type { AdbScrcpyClientOptions } from "./client-options.js";
|
||||||
import type { AdbScrcpyConnection } from "./connection.js";
|
import type { AdbScrcpyConnection } from "./connection.js";
|
||||||
import { AdbScrcpyOptions } from "./types.js";
|
import { AdbScrcpyOptions } from "./types.js";
|
||||||
|
|
||||||
export class AdbScrcpyOptions3_1<
|
export class AdbScrcpyOptions3_1<
|
||||||
TVideo extends boolean,
|
TVideo extends boolean,
|
||||||
> extends AdbScrcpyOptions<ScrcpyOptions3_1.Init<TVideo>> {
|
> extends AdbScrcpyOptions<ScrcpyOptions3_1.Init<TVideo>> {
|
||||||
constructor(init: ScrcpyOptions3_1.Init<TVideo>, version?: string) {
|
constructor(
|
||||||
super(new ScrcpyOptions3_1(init, version));
|
init: ScrcpyOptions3_1.Init<TVideo>,
|
||||||
|
clientOptions?: AdbScrcpyClientOptions,
|
||||||
|
) {
|
||||||
|
super(
|
||||||
|
new ScrcpyOptions3_1(init, clientOptions?.version),
|
||||||
|
clientOptions?.spawner,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
override getEncoders(adb: Adb, path: string): Promise<ScrcpyEncoder[]> {
|
override getEncoders(adb: Adb, path: string): Promise<ScrcpyEncoder[]> {
|
||||||
|
|
6
libraries/adb-scrcpy/src/client-options.ts
Normal file
6
libraries/adb-scrcpy/src/client-options.ts
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
import type { AdbNoneProtocolSpawner } from "@yume-chan/adb";
|
||||||
|
|
||||||
|
export interface AdbScrcpyClientOptions {
|
||||||
|
version?: string;
|
||||||
|
spawner?: AdbNoneProtocolSpawner | undefined;
|
||||||
|
}
|
|
@ -1,8 +1,5 @@
|
||||||
import type { Adb, AdbSubprocessProtocol } from "@yume-chan/adb";
|
import type { Adb, AdbNoneProtocolProcess } from "@yume-chan/adb";
|
||||||
import {
|
import { AdbReverseNotSupportedError } from "@yume-chan/adb";
|
||||||
AdbReverseNotSupportedError,
|
|
||||||
AdbSubprocessNoneProtocol,
|
|
||||||
} from "@yume-chan/adb";
|
|
||||||
import type {
|
import type {
|
||||||
ScrcpyAudioStreamDisabledMetadata,
|
ScrcpyAudioStreamDisabledMetadata,
|
||||||
ScrcpyAudioStreamErroredMetadata,
|
ScrcpyAudioStreamErroredMetadata,
|
||||||
|
@ -70,7 +67,7 @@ export class AdbScrcpyExitedError extends Error {
|
||||||
|
|
||||||
interface AdbScrcpyClientInit<TOptions extends AdbScrcpyOptions<object>> {
|
interface AdbScrcpyClientInit<TOptions extends AdbScrcpyOptions<object>> {
|
||||||
options: TOptions;
|
options: TOptions;
|
||||||
process: AdbSubprocessProtocol;
|
process: AdbNoneProtocolProcess;
|
||||||
stdout: ReadableStream<string>;
|
stdout: ReadableStream<string>;
|
||||||
|
|
||||||
videoStream: ReadableStream<Uint8Array> | undefined;
|
videoStream: ReadableStream<Uint8Array> | undefined;
|
||||||
|
@ -117,7 +114,7 @@ export class AdbScrcpyClient<TOptions extends AdbScrcpyOptions<object>> {
|
||||||
options: TOptions,
|
options: TOptions,
|
||||||
): Promise<AdbScrcpyClient<TOptions>> {
|
): Promise<AdbScrcpyClient<TOptions>> {
|
||||||
let connection: AdbScrcpyConnection | undefined;
|
let connection: AdbScrcpyConnection | undefined;
|
||||||
let process: AdbSubprocessProtocol | undefined;
|
let process: AdbNoneProtocolProcess | undefined;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
try {
|
try {
|
||||||
|
@ -135,35 +132,34 @@ export class AdbScrcpyClient<TOptions extends AdbScrcpyOptions<object>> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
process = await adb.subprocess.spawn(
|
const args = [
|
||||||
[
|
"app_process",
|
||||||
// cspell: disable-next-line
|
"-cp",
|
||||||
`CLASSPATH=${path}`,
|
path,
|
||||||
"app_process",
|
/* unused */ "/",
|
||||||
/* unused */ "/",
|
"com.genymobile.scrcpy.Server",
|
||||||
"com.genymobile.scrcpy.Server",
|
options.version,
|
||||||
options.version,
|
...options.serialize(),
|
||||||
...options.serialize(),
|
];
|
||||||
],
|
|
||||||
{
|
|
||||||
// Scrcpy server doesn't use stderr,
|
|
||||||
// so disable Shell Protocol to simplify processing
|
|
||||||
protocols: [AdbSubprocessNoneProtocol],
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const stdout = process.stdout
|
if (options.spawner) {
|
||||||
|
process = await options.spawner.spawn(args);
|
||||||
|
} else {
|
||||||
|
process = await adb.subprocess.noneProtocol.spawn(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
const output = process.output
|
||||||
.pipeThrough(new TextDecoderStream())
|
.pipeThrough(new TextDecoderStream())
|
||||||
.pipeThrough(new SplitStringStream("\n"));
|
.pipeThrough(new SplitStringStream("\n"));
|
||||||
|
|
||||||
// Must read all streams, otherwise the whole connection will be blocked.
|
// Must read all streams, otherwise the whole connection will be blocked.
|
||||||
const output: string[] = [];
|
const lines: string[] = [];
|
||||||
const abortController = new AbortController();
|
const abortController = new AbortController();
|
||||||
const pipe = stdout
|
const pipe = output
|
||||||
.pipeTo(
|
.pipeTo(
|
||||||
new WritableStream({
|
new WritableStream({
|
||||||
write(chunk) {
|
write(chunk) {
|
||||||
output.push(chunk);
|
lines.push(chunk);
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
|
@ -180,8 +176,8 @@ export class AdbScrcpyClient<TOptions extends AdbScrcpyOptions<object>> {
|
||||||
});
|
});
|
||||||
|
|
||||||
const streams = await Promise.race([
|
const streams = await Promise.race([
|
||||||
process.exit.then(() => {
|
process.exited.then(() => {
|
||||||
throw new AdbScrcpyExitedError(output);
|
throw new AdbScrcpyExitedError(lines);
|
||||||
}),
|
}),
|
||||||
connection.getStreams(),
|
connection.getStreams(),
|
||||||
]);
|
]);
|
||||||
|
@ -192,7 +188,7 @@ export class AdbScrcpyClient<TOptions extends AdbScrcpyOptions<object>> {
|
||||||
return new AdbScrcpyClient({
|
return new AdbScrcpyClient({
|
||||||
options,
|
options,
|
||||||
process,
|
process,
|
||||||
stdout: concatStreams(arrayToStream(output), stdout),
|
stdout: concatStreams(arrayToStream(lines), output),
|
||||||
videoStream: streams.video,
|
videoStream: streams.video,
|
||||||
audioStream: streams.audio,
|
audioStream: streams.audio,
|
||||||
controlStream: streams.control,
|
controlStream: streams.control,
|
||||||
|
@ -232,15 +228,15 @@ export class AdbScrcpyClient<TOptions extends AdbScrcpyOptions<object>> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#options: TOptions;
|
#options: TOptions;
|
||||||
#process: AdbSubprocessProtocol;
|
#process: AdbNoneProtocolProcess;
|
||||||
|
|
||||||
#stdout: ReadableStream<string>;
|
#stdout: ReadableStream<string>;
|
||||||
get stdout() {
|
get stdout() {
|
||||||
return this.#stdout;
|
return this.#stdout;
|
||||||
}
|
}
|
||||||
|
|
||||||
get exit() {
|
get exited() {
|
||||||
return this.#process.exit;
|
return this.#process.exited;
|
||||||
}
|
}
|
||||||
|
|
||||||
#videoStream: Promise<AdbScrcpyVideoStream> | undefined;
|
#videoStream: Promise<AdbScrcpyVideoStream> | undefined;
|
||||||
|
|
|
@ -22,6 +22,8 @@ export * from "./3_0.js";
|
||||||
export * from "./3_0_1.js";
|
export * from "./3_0_1.js";
|
||||||
export * from "./3_0_2.js";
|
export * from "./3_0_2.js";
|
||||||
export * from "./3_1.js";
|
export * from "./3_1.js";
|
||||||
|
export * from "./client-options.js";
|
||||||
export * from "./client.js";
|
export * from "./client.js";
|
||||||
export * from "./connection.js";
|
export * from "./connection.js";
|
||||||
export * from "./latest.js";
|
export * from "./latest.js";
|
||||||
|
export * from "./types.js";
|
||||||
|
|
|
@ -1,10 +1,14 @@
|
||||||
import { AdbScrcpyOptions3_1 } from "./3_1.js";
|
import { AdbScrcpyOptions3_1 } from "./3_1.js";
|
||||||
|
import type { AdbScrcpyClientOptions } from "./client-options.js";
|
||||||
|
|
||||||
export class AdbScrcpyOptionsLatest<
|
export class AdbScrcpyOptionsLatest<
|
||||||
TVideo extends boolean,
|
TVideo extends boolean,
|
||||||
> extends AdbScrcpyOptions3_1<TVideo> {
|
> extends AdbScrcpyOptions3_1<TVideo> {
|
||||||
constructor(init: AdbScrcpyOptions3_1.Init<TVideo>, version: string) {
|
constructor(
|
||||||
super(init, version);
|
init: AdbScrcpyOptions3_1.Init<TVideo>,
|
||||||
|
clientOptions?: AdbScrcpyClientOptions,
|
||||||
|
) {
|
||||||
|
super(init, clientOptions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
import type { Adb } from "@yume-chan/adb";
|
import type { Adb, AdbNoneProtocolSpawner } from "@yume-chan/adb";
|
||||||
import type { ScrcpyDisplay, ScrcpyEncoder } from "@yume-chan/scrcpy";
|
import type {
|
||||||
|
ScrcpyDisplay,
|
||||||
|
ScrcpyEncoder,
|
||||||
|
ScrcpyOptions,
|
||||||
|
} from "@yume-chan/scrcpy";
|
||||||
import { ScrcpyOptionsWrapper } from "@yume-chan/scrcpy";
|
import { ScrcpyOptionsWrapper } from "@yume-chan/scrcpy";
|
||||||
|
|
||||||
import type { AdbScrcpyConnection } from "./connection.js";
|
import type { AdbScrcpyConnection } from "./connection.js";
|
||||||
|
@ -7,6 +11,16 @@ import type { AdbScrcpyConnection } from "./connection.js";
|
||||||
export abstract class AdbScrcpyOptions<
|
export abstract class AdbScrcpyOptions<
|
||||||
T extends object,
|
T extends object,
|
||||||
> extends ScrcpyOptionsWrapper<T> {
|
> extends ScrcpyOptionsWrapper<T> {
|
||||||
|
#spawner: AdbNoneProtocolSpawner | undefined;
|
||||||
|
get spawner() {
|
||||||
|
return this.#spawner;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(base: ScrcpyOptions<T>, spawner?: AdbNoneProtocolSpawner) {
|
||||||
|
super(base);
|
||||||
|
this.#spawner = spawner;
|
||||||
|
}
|
||||||
|
|
||||||
abstract getEncoders(adb: Adb, path: string): Promise<ScrcpyEncoder[]>;
|
abstract getEncoders(adb: Adb, path: string): Promise<ScrcpyEncoder[]>;
|
||||||
|
|
||||||
abstract getDisplays(adb: Adb, path: string): Promise<ScrcpyDisplay[]>;
|
abstract getDisplays(adb: Adb, path: string): Promise<ScrcpyDisplay[]>;
|
||||||
|
|
|
@ -14,7 +14,7 @@ function nodeSocketToConnection(
|
||||||
): AdbServerClient.ServerConnection {
|
): AdbServerClient.ServerConnection {
|
||||||
socket.setNoDelay(true);
|
socket.setNoDelay(true);
|
||||||
|
|
||||||
const closed = new Promise<void>((resolve) => {
|
const closed = new Promise<undefined>((resolve) => {
|
||||||
socket.on("close", resolve);
|
socket.on("close", resolve);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ import type { AdbFrameBuffer } from "./commands/index.js";
|
||||||
import {
|
import {
|
||||||
AdbPower,
|
AdbPower,
|
||||||
AdbReverseCommand,
|
AdbReverseCommand,
|
||||||
AdbSubprocess,
|
AdbSubprocessService,
|
||||||
AdbSync,
|
AdbSync,
|
||||||
AdbTcpIpCommand,
|
AdbTcpIpCommand,
|
||||||
escapeArg,
|
escapeArg,
|
||||||
|
@ -30,7 +30,7 @@ export interface AdbSocket
|
||||||
Closeable {
|
Closeable {
|
||||||
get service(): string;
|
get service(): string;
|
||||||
|
|
||||||
get closed(): Promise<void>;
|
get closed(): Promise<undefined>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AdbIncomingSocketHandler = (
|
export type AdbIncomingSocketHandler = (
|
||||||
|
@ -87,7 +87,7 @@ export class Adb implements Closeable {
|
||||||
return this.banner.features;
|
return this.banner.features;
|
||||||
}
|
}
|
||||||
|
|
||||||
readonly subprocess: AdbSubprocess;
|
readonly subprocess: AdbSubprocessService;
|
||||||
readonly power: AdbPower;
|
readonly power: AdbPower;
|
||||||
readonly reverse: AdbReverseCommand;
|
readonly reverse: AdbReverseCommand;
|
||||||
readonly tcpip: AdbTcpIpCommand;
|
readonly tcpip: AdbTcpIpCommand;
|
||||||
|
@ -95,7 +95,7 @@ export class Adb implements Closeable {
|
||||||
constructor(transport: AdbTransport) {
|
constructor(transport: AdbTransport) {
|
||||||
this.transport = transport;
|
this.transport = transport;
|
||||||
|
|
||||||
this.subprocess = new AdbSubprocess(this);
|
this.subprocess = new AdbSubprocessService(this);
|
||||||
this.power = new AdbPower(this);
|
this.power = new AdbPower(this);
|
||||||
this.reverse = new AdbReverseCommand(this);
|
this.reverse = new AdbReverseCommand(this);
|
||||||
this.tcpip = new AdbTcpIpCommand(this);
|
this.tcpip = new AdbTcpIpCommand(this);
|
||||||
|
@ -122,15 +122,13 @@ export class Adb implements Closeable {
|
||||||
.pipeThrough(new ConcatStringStream());
|
.pipeThrough(new ConcatStringStream());
|
||||||
}
|
}
|
||||||
|
|
||||||
async getProp(key: string): Promise<string> {
|
getProp(key: string): Promise<string> {
|
||||||
const stdout = await this.subprocess.spawnAndWaitLegacy([
|
return this.subprocess.noneProtocol
|
||||||
"getprop",
|
.spawnWaitText(["getprop", key])
|
||||||
key,
|
.then((output) => output.trim());
|
||||||
]);
|
|
||||||
return stdout.trim();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async rm(
|
rm(
|
||||||
filenames: string | string[],
|
filenames: string | string[],
|
||||||
options?: { recursive?: boolean; force?: boolean },
|
options?: { recursive?: boolean; force?: boolean },
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
|
@ -150,8 +148,8 @@ export class Adb implements Closeable {
|
||||||
}
|
}
|
||||||
// https://android.googlesource.com/platform/packages/modules/adb/+/1a0fb8846d4e6b671c8aa7f137a8c21d7b248716/client/adb_install.cpp#984
|
// https://android.googlesource.com/platform/packages/modules/adb/+/1a0fb8846d4e6b671c8aa7f137a8c21d7b248716/client/adb_install.cpp#984
|
||||||
args.push("</dev/null");
|
args.push("</dev/null");
|
||||||
const stdout = await this.subprocess.spawnAndWaitLegacy(args);
|
|
||||||
return stdout;
|
return this.subprocess.noneProtocol.spawnWaitText(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
async sync(): Promise<AdbSync> {
|
async sync(): Promise<AdbSync> {
|
||||||
|
|
|
@ -2,11 +2,14 @@ import { AutoDisposable } from "@yume-chan/event";
|
||||||
|
|
||||||
import type { Adb } from "../adb.js";
|
import type { Adb } from "../adb.js";
|
||||||
|
|
||||||
export class AdbCommandBase extends AutoDisposable {
|
export class AdbServiceBase extends AutoDisposable {
|
||||||
protected adb: Adb;
|
#adb: Adb;
|
||||||
|
get adb() {
|
||||||
|
return this.#adb;
|
||||||
|
}
|
||||||
|
|
||||||
constructor(adb: Adb) {
|
constructor(adb: Adb) {
|
||||||
super();
|
super();
|
||||||
this.adb = adb;
|
this.#adb = adb;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,9 +3,9 @@
|
||||||
// cspell: ignore keyevent
|
// cspell: ignore keyevent
|
||||||
// cspell: ignore longpress
|
// cspell: ignore longpress
|
||||||
|
|
||||||
import { AdbCommandBase } from "./base.js";
|
import { AdbServiceBase } from "./base.js";
|
||||||
|
|
||||||
export class AdbPower extends AdbCommandBase {
|
export class AdbPower extends AdbServiceBase {
|
||||||
reboot(mode = "") {
|
reboot(mode = "") {
|
||||||
return this.adb.createSocketAndWait(`reboot:${mode}`);
|
return this.adb.createSocketAndWait(`reboot:${mode}`);
|
||||||
}
|
}
|
||||||
|
@ -35,18 +35,18 @@ export class AdbPower extends AdbCommandBase {
|
||||||
return this.reboot("edl");
|
return this.reboot("edl");
|
||||||
}
|
}
|
||||||
|
|
||||||
powerOff() {
|
powerOff(): Promise<string> {
|
||||||
return this.adb.subprocess.spawnAndWaitLegacy(["reboot", "-p"]);
|
return this.adb.subprocess.noneProtocol.spawnWaitText(["reboot", "-p"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
powerButton(longPress = false) {
|
powerButton(longPress = false): Promise<string> {
|
||||||
const args = ["input", "keyevent"];
|
const args = ["input", "keyevent"];
|
||||||
if (longPress) {
|
if (longPress) {
|
||||||
args.push("--longpress");
|
args.push("--longpress");
|
||||||
}
|
}
|
||||||
args.push("POWER");
|
args.push("POWER");
|
||||||
|
|
||||||
return this.adb.subprocess.spawnAndWaitLegacy(args);
|
return this.adb.subprocess.noneProtocol.spawnWaitText(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,144 +0,0 @@
|
||||||
import type { AbortSignal } from "@yume-chan/stream-extra";
|
|
||||||
import { ConcatStringStream, TextDecoderStream } from "@yume-chan/stream-extra";
|
|
||||||
|
|
||||||
import { AdbCommandBase } from "../base.js";
|
|
||||||
|
|
||||||
import type {
|
|
||||||
AdbSubprocessProtocol,
|
|
||||||
AdbSubprocessProtocolConstructor,
|
|
||||||
} from "./protocols/index.js";
|
|
||||||
import {
|
|
||||||
AdbSubprocessNoneProtocol,
|
|
||||||
AdbSubprocessShellProtocol,
|
|
||||||
} from "./protocols/index.js";
|
|
||||||
|
|
||||||
export interface AdbSubprocessOptions {
|
|
||||||
/**
|
|
||||||
* A list of `AdbSubprocessProtocolConstructor`s to be used.
|
|
||||||
*
|
|
||||||
* Different `AdbSubprocessProtocol` has different capabilities, thus requires specific adaptations.
|
|
||||||
* Check their documentations for details.
|
|
||||||
*
|
|
||||||
* The first protocol whose `isSupported` returns `true` will be used.
|
|
||||||
* If no `AdbSubprocessProtocol` is supported, an error will be thrown.
|
|
||||||
*
|
|
||||||
* @default [AdbSubprocessShellProtocol, AdbSubprocessNoneProtocol]
|
|
||||||
*/
|
|
||||||
protocols: AdbSubprocessProtocolConstructor[];
|
|
||||||
|
|
||||||
signal?: AbortSignal;
|
|
||||||
}
|
|
||||||
|
|
||||||
const DEFAULT_OPTIONS = {
|
|
||||||
protocols: [AdbSubprocessShellProtocol, AdbSubprocessNoneProtocol],
|
|
||||||
} satisfies AdbSubprocessOptions;
|
|
||||||
|
|
||||||
export interface AdbSubprocessWaitResult {
|
|
||||||
stdout: string;
|
|
||||||
stderr: string;
|
|
||||||
exitCode: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class AdbSubprocess extends AdbCommandBase {
|
|
||||||
async #createProtocol(
|
|
||||||
mode: "pty" | "raw",
|
|
||||||
command?: string | string[],
|
|
||||||
options?: Partial<AdbSubprocessOptions>,
|
|
||||||
): Promise<AdbSubprocessProtocol> {
|
|
||||||
const { protocols, signal } = { ...DEFAULT_OPTIONS, ...options };
|
|
||||||
|
|
||||||
let Constructor: AdbSubprocessProtocolConstructor | undefined;
|
|
||||||
for (const item of protocols) {
|
|
||||||
// It's async so can't use `Array#find`
|
|
||||||
if (await item.isSupported(this.adb)) {
|
|
||||||
Constructor = item;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Constructor) {
|
|
||||||
throw new Error("No specified protocol is supported by the device");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Array.isArray(command)) {
|
|
||||||
command = command.join(" ");
|
|
||||||
} else if (command === undefined) {
|
|
||||||
// spawn the default shell
|
|
||||||
command = "";
|
|
||||||
}
|
|
||||||
return await Constructor[mode](this.adb, command, signal);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Spawns an executable in PTY mode.
|
|
||||||
*
|
|
||||||
* Redirection mode is enough for most simple commands, but PTY mode is required for
|
|
||||||
* commands that manipulate the terminal, such as `vi` and `less`.
|
|
||||||
* @param command The command to run. If omitted, the default shell will be spawned.
|
|
||||||
* @param options The options for creating the `AdbSubprocessProtocol`
|
|
||||||
* @returns A new `AdbSubprocessProtocol` instance connecting to the spawned process.
|
|
||||||
*/
|
|
||||||
shell(
|
|
||||||
command?: string | string[],
|
|
||||||
options?: Partial<AdbSubprocessOptions>,
|
|
||||||
): Promise<AdbSubprocessProtocol> {
|
|
||||||
return this.#createProtocol("pty", command, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Spawns an executable and redirect the standard input/output stream.
|
|
||||||
*
|
|
||||||
* Redirection mode is enough for most simple commands, but PTY mode is required for
|
|
||||||
* commands that manipulate the terminal, such as `vi` and `less`.
|
|
||||||
* @param command The command to run, or an array of strings containing both command and args.
|
|
||||||
* @param options The options for creating the `AdbSubprocessProtocol`
|
|
||||||
* @returns A new `AdbSubprocessProtocol` instance connecting to the spawned process.
|
|
||||||
*/
|
|
||||||
spawn(
|
|
||||||
command: string | string[],
|
|
||||||
options?: Partial<AdbSubprocessOptions>,
|
|
||||||
): Promise<AdbSubprocessProtocol> {
|
|
||||||
return this.#createProtocol("raw", command, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Spawns a new process, waits until it exits, and returns the entire output.
|
|
||||||
* @param command The command to run
|
|
||||||
* @param options The options for creating the `AdbSubprocessProtocol`
|
|
||||||
* @returns The entire output of the command
|
|
||||||
*/
|
|
||||||
async spawnAndWait(
|
|
||||||
command: string | string[],
|
|
||||||
options?: Partial<AdbSubprocessOptions>,
|
|
||||||
): Promise<AdbSubprocessWaitResult> {
|
|
||||||
const process = await this.spawn(command, options);
|
|
||||||
|
|
||||||
const [stdout, stderr, exitCode] = await Promise.all([
|
|
||||||
process.stdout
|
|
||||||
.pipeThrough(new TextDecoderStream())
|
|
||||||
.pipeThrough(new ConcatStringStream()),
|
|
||||||
process.stderr
|
|
||||||
.pipeThrough(new TextDecoderStream())
|
|
||||||
.pipeThrough(new ConcatStringStream()),
|
|
||||||
process.exit,
|
|
||||||
]);
|
|
||||||
|
|
||||||
return {
|
|
||||||
stdout,
|
|
||||||
stderr,
|
|
||||||
exitCode,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Spawns a new process, waits until it exits, and returns the entire output.
|
|
||||||
* @param command The command to run
|
|
||||||
* @returns The entire output of the command
|
|
||||||
*/
|
|
||||||
async spawnAndWaitLegacy(command: string | string[]): Promise<string> {
|
|
||||||
const { stdout } = await this.spawnAndWait(command, {
|
|
||||||
protocols: [AdbSubprocessNoneProtocol],
|
|
||||||
});
|
|
||||||
return stdout;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,3 +1,4 @@
|
||||||
export * from "./command.js";
|
export * from "./none/index.js";
|
||||||
export * from "./protocols/index.js";
|
export * from "./service.js";
|
||||||
|
export * from "./shell/index.js";
|
||||||
export * from "./utils.js";
|
export * from "./utils.js";
|
||||||
|
|
4
libraries/adb/src/commands/subprocess/none/index.ts
Normal file
4
libraries/adb/src/commands/subprocess/none/index.ts
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
export * from "./process.js";
|
||||||
|
export * from "./pty.js";
|
||||||
|
export * from "./service.js";
|
||||||
|
export * from "./spawner.js";
|
|
@ -6,15 +6,15 @@ import { ReadableStream, WritableStream } from "@yume-chan/stream-extra";
|
||||||
|
|
||||||
import type { AdbSocket } from "../../../adb.js";
|
import type { AdbSocket } from "../../../adb.js";
|
||||||
|
|
||||||
import { AdbSubprocessNoneProtocol } from "./none.js";
|
import { AdbNoneProtocolProcessImpl } from "./process.js";
|
||||||
|
|
||||||
describe("AdbSubprocessNoneProtocol", () => {
|
describe("AdbNoneProtocolProcessImpl", () => {
|
||||||
describe("stdout", () => {
|
describe("output", () => {
|
||||||
it("should pipe data from `socket`", async () => {
|
it("should pipe data from `socket`", async () => {
|
||||||
const closed = new PromiseResolver<void>();
|
const closed = new PromiseResolver<undefined>();
|
||||||
const socket: AdbSocket = {
|
const socket = {
|
||||||
service: "",
|
service: "",
|
||||||
close: mock.fn(() => {}),
|
close: () => {},
|
||||||
closed: closed.promise,
|
closed: closed.promise,
|
||||||
readable: new ReadableStream({
|
readable: new ReadableStream({
|
||||||
async start(controller) {
|
async start(controller) {
|
||||||
|
@ -25,10 +25,10 @@ describe("AdbSubprocessNoneProtocol", () => {
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
writable: new WritableStream(),
|
writable: new WritableStream(),
|
||||||
};
|
} satisfies AdbSocket;
|
||||||
|
|
||||||
const process = new AdbSubprocessNoneProtocol(socket);
|
const process = new AdbNoneProtocolProcessImpl(socket);
|
||||||
const reader = process.stdout.getReader();
|
const reader = process.output.getReader();
|
||||||
|
|
||||||
assert.deepStrictEqual(await reader.read(), {
|
assert.deepStrictEqual(await reader.read(), {
|
||||||
done: false,
|
done: false,
|
||||||
|
@ -41,8 +41,8 @@ describe("AdbSubprocessNoneProtocol", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should close when `socket` is closed", async () => {
|
it("should close when `socket` is closed", async () => {
|
||||||
const closed = new PromiseResolver<void>();
|
const closed = new PromiseResolver<undefined>();
|
||||||
const socket: AdbSocket = {
|
const socket = {
|
||||||
service: "",
|
service: "",
|
||||||
close: mock.fn(() => {}),
|
close: mock.fn(() => {}),
|
||||||
closed: closed.promise,
|
closed: closed.promise,
|
||||||
|
@ -55,10 +55,10 @@ describe("AdbSubprocessNoneProtocol", () => {
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
writable: new WritableStream(),
|
writable: new WritableStream(),
|
||||||
};
|
} satisfies AdbSocket;
|
||||||
|
|
||||||
const process = new AdbSubprocessNoneProtocol(socket);
|
const process = new AdbNoneProtocolProcessImpl(socket);
|
||||||
const reader = process.stdout.getReader();
|
const reader = process.output.getReader();
|
||||||
|
|
||||||
assert.deepStrictEqual(await reader.read(), {
|
assert.deepStrictEqual(await reader.read(), {
|
||||||
done: false,
|
done: false,
|
||||||
|
@ -69,37 +69,7 @@ describe("AdbSubprocessNoneProtocol", () => {
|
||||||
value: new Uint8Array([4, 5, 6]),
|
value: new Uint8Array([4, 5, 6]),
|
||||||
});
|
});
|
||||||
|
|
||||||
closed.resolve();
|
closed.resolve(undefined);
|
||||||
|
|
||||||
assert.deepStrictEqual(await reader.read(), {
|
|
||||||
done: true,
|
|
||||||
value: undefined,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("stderr", () => {
|
|
||||||
it("should be empty", async () => {
|
|
||||||
const closed = new PromiseResolver<void>();
|
|
||||||
const socket: AdbSocket = {
|
|
||||||
service: "",
|
|
||||||
close: mock.fn(() => {}),
|
|
||||||
closed: closed.promise,
|
|
||||||
readable: new ReadableStream({
|
|
||||||
async start(controller) {
|
|
||||||
controller.enqueue(new Uint8Array([1, 2, 3]));
|
|
||||||
controller.enqueue(new Uint8Array([4, 5, 6]));
|
|
||||||
await closed.promise;
|
|
||||||
controller.close();
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
writable: new WritableStream(),
|
|
||||||
};
|
|
||||||
|
|
||||||
const process = new AdbSubprocessNoneProtocol(socket);
|
|
||||||
const reader = process.stderr.getReader();
|
|
||||||
|
|
||||||
closed.resolve();
|
|
||||||
|
|
||||||
assert.deepStrictEqual(await reader.read(), {
|
assert.deepStrictEqual(await reader.read(), {
|
||||||
done: true,
|
done: true,
|
||||||
|
@ -110,48 +80,35 @@ describe("AdbSubprocessNoneProtocol", () => {
|
||||||
|
|
||||||
describe("exit", () => {
|
describe("exit", () => {
|
||||||
it("should resolve when `socket` closes", async () => {
|
it("should resolve when `socket` closes", async () => {
|
||||||
const closed = new PromiseResolver<void>();
|
const closed = new PromiseResolver<undefined>();
|
||||||
const socket: AdbSocket = {
|
const socket = {
|
||||||
service: "",
|
service: "",
|
||||||
close: mock.fn(() => {}),
|
close: () => {},
|
||||||
closed: closed.promise,
|
closed: closed.promise,
|
||||||
readable: new ReadableStream(),
|
readable: new ReadableStream(),
|
||||||
writable: new WritableStream(),
|
writable: new WritableStream(),
|
||||||
};
|
} satisfies AdbSocket;
|
||||||
|
|
||||||
const process = new AdbSubprocessNoneProtocol(socket);
|
const process = new AdbNoneProtocolProcessImpl(socket);
|
||||||
|
|
||||||
closed.resolve();
|
closed.resolve(undefined);
|
||||||
|
|
||||||
assert.strictEqual(await process.exit, 0);
|
assert.strictEqual(await process.exited, undefined);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("`resize` shouldn't throw any error", () => {
|
it("`kill` should close `socket`", async () => {
|
||||||
const socket: AdbSocket = {
|
const socket = {
|
||||||
service: "",
|
service: "",
|
||||||
close: mock.fn(() => {}),
|
close: mock.fn(() => {}),
|
||||||
closed: new PromiseResolver<void>().promise,
|
closed: new PromiseResolver<undefined>().promise,
|
||||||
readable: new ReadableStream(),
|
readable: new ReadableStream(),
|
||||||
writable: new WritableStream(),
|
writable: new WritableStream(),
|
||||||
};
|
} satisfies AdbSocket;
|
||||||
|
|
||||||
const process = new AdbSubprocessNoneProtocol(socket);
|
const process = new AdbNoneProtocolProcessImpl(socket);
|
||||||
assert.doesNotThrow(() => process.resize());
|
|
||||||
});
|
|
||||||
|
|
||||||
it("`kill` should close `socket`", async () => {
|
|
||||||
const close = mock.fn(() => {});
|
|
||||||
const socket: AdbSocket = {
|
|
||||||
service: "",
|
|
||||||
close,
|
|
||||||
closed: new PromiseResolver<void>().promise,
|
|
||||||
readable: new ReadableStream(),
|
|
||||||
writable: new WritableStream(),
|
|
||||||
};
|
|
||||||
|
|
||||||
const process = new AdbSubprocessNoneProtocol(socket);
|
|
||||||
await process.kill();
|
await process.kill();
|
||||||
assert.deepEqual(close.mock.callCount(), 1);
|
|
||||||
|
assert.deepEqual(socket.close.mock.callCount(), 1);
|
||||||
});
|
});
|
||||||
});
|
});
|
56
libraries/adb/src/commands/subprocess/none/process.ts
Normal file
56
libraries/adb/src/commands/subprocess/none/process.ts
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
import type { MaybePromiseLike } from "@yume-chan/async";
|
||||||
|
import { PromiseResolver } from "@yume-chan/async";
|
||||||
|
import type {
|
||||||
|
AbortSignal,
|
||||||
|
MaybeConsumable,
|
||||||
|
ReadableStream,
|
||||||
|
WritableStream,
|
||||||
|
} from "@yume-chan/stream-extra";
|
||||||
|
|
||||||
|
import type { AdbSocket } from "../../../adb.js";
|
||||||
|
|
||||||
|
import type { AdbNoneProtocolProcess } from "./spawner.js";
|
||||||
|
|
||||||
|
export class AdbNoneProtocolProcessImpl implements AdbNoneProtocolProcess {
|
||||||
|
readonly #socket: AdbSocket;
|
||||||
|
|
||||||
|
get stdin(): WritableStream<MaybeConsumable<Uint8Array>> {
|
||||||
|
return this.#socket.writable;
|
||||||
|
}
|
||||||
|
|
||||||
|
get output(): ReadableStream<Uint8Array> {
|
||||||
|
return this.#socket.readable;
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly #exited: Promise<undefined>;
|
||||||
|
get exited(): Promise<undefined> {
|
||||||
|
return this.#exited;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(socket: AdbSocket, signal?: AbortSignal) {
|
||||||
|
this.#socket = socket;
|
||||||
|
|
||||||
|
if (signal) {
|
||||||
|
// `signal` won't affect `this.output`
|
||||||
|
// So remaining data can still be read
|
||||||
|
// (call `controller.error` will discard all pending data)
|
||||||
|
|
||||||
|
const exited = new PromiseResolver<undefined>();
|
||||||
|
this.#socket.closed.then(
|
||||||
|
() => exited.resolve(undefined),
|
||||||
|
(e) => exited.reject(e),
|
||||||
|
);
|
||||||
|
signal.addEventListener("abort", () => {
|
||||||
|
exited.reject(signal.reason);
|
||||||
|
this.#socket.close();
|
||||||
|
});
|
||||||
|
this.#exited = exited.promise;
|
||||||
|
} else {
|
||||||
|
this.#exited = this.#socket.closed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
kill(): MaybePromiseLike<void> {
|
||||||
|
return this.#socket.close();
|
||||||
|
}
|
||||||
|
}
|
45
libraries/adb/src/commands/subprocess/none/pty.ts
Normal file
45
libraries/adb/src/commands/subprocess/none/pty.ts
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
import type { MaybePromiseLike } from "@yume-chan/async";
|
||||||
|
import type {
|
||||||
|
ReadableStream,
|
||||||
|
WritableStream,
|
||||||
|
WritableStreamDefaultWriter,
|
||||||
|
} from "@yume-chan/stream-extra";
|
||||||
|
import { MaybeConsumable } from "@yume-chan/stream-extra";
|
||||||
|
|
||||||
|
import type { AdbSocket } from "../../../adb.js";
|
||||||
|
import type { AdbPtyProcess } from "../pty.js";
|
||||||
|
|
||||||
|
export class AdbNoneProtocolPtyProcess implements AdbPtyProcess<undefined> {
|
||||||
|
readonly #socket: AdbSocket;
|
||||||
|
readonly #writer: WritableStreamDefaultWriter<MaybeConsumable<Uint8Array>>;
|
||||||
|
|
||||||
|
readonly #input: MaybeConsumable.WritableStream<Uint8Array>;
|
||||||
|
get input(): WritableStream<MaybeConsumable<Uint8Array>> {
|
||||||
|
return this.#input;
|
||||||
|
}
|
||||||
|
|
||||||
|
get output(): ReadableStream<Uint8Array> {
|
||||||
|
return this.#socket.readable;
|
||||||
|
}
|
||||||
|
|
||||||
|
get exited(): Promise<undefined> {
|
||||||
|
return this.#socket.closed;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(socket: AdbSocket) {
|
||||||
|
this.#socket = socket;
|
||||||
|
|
||||||
|
this.#writer = this.#socket.writable.getWriter();
|
||||||
|
this.#input = new MaybeConsumable.WritableStream<Uint8Array>({
|
||||||
|
write: (chunk) => this.#writer.write(chunk),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
sigint() {
|
||||||
|
return this.#writer.write(new Uint8Array([0x03]));
|
||||||
|
}
|
||||||
|
|
||||||
|
kill(): MaybePromiseLike<void> {
|
||||||
|
return this.#socket.close();
|
||||||
|
}
|
||||||
|
}
|
42
libraries/adb/src/commands/subprocess/none/service.ts
Normal file
42
libraries/adb/src/commands/subprocess/none/service.ts
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
import type { Adb } from "../../../adb.js";
|
||||||
|
|
||||||
|
import { AdbNoneProtocolProcessImpl } from "./process.js";
|
||||||
|
import { AdbNoneProtocolPtyProcess } from "./pty.js";
|
||||||
|
import { AdbNoneProtocolSpawner } from "./spawner.js";
|
||||||
|
|
||||||
|
export class AdbNoneProtocolSubprocessService extends AdbNoneProtocolSpawner {
|
||||||
|
readonly #adb: Adb;
|
||||||
|
get adb(): Adb {
|
||||||
|
return this.#adb;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(adb: Adb) {
|
||||||
|
super(async (command, signal) => {
|
||||||
|
// `shell,raw:${command}` also triggers raw mode,
|
||||||
|
// But is not supported on Android version <7.
|
||||||
|
const socket = await this.#adb.createSocket(
|
||||||
|
`exec:${command.join(" ")}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (signal?.aborted) {
|
||||||
|
await socket.close();
|
||||||
|
throw signal.reason;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new AdbNoneProtocolProcessImpl(socket, signal);
|
||||||
|
});
|
||||||
|
this.#adb = adb;
|
||||||
|
}
|
||||||
|
|
||||||
|
async pty(command?: string | string[]): Promise<AdbNoneProtocolPtyProcess> {
|
||||||
|
if (command === undefined) {
|
||||||
|
command = "";
|
||||||
|
} else if (Array.isArray(command)) {
|
||||||
|
command = command.join(" ");
|
||||||
|
}
|
||||||
|
|
||||||
|
return new AdbNoneProtocolPtyProcess(
|
||||||
|
await this.#adb.createSocket(`shell:${command}`),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
68
libraries/adb/src/commands/subprocess/none/spawner.ts
Normal file
68
libraries/adb/src/commands/subprocess/none/spawner.ts
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
import type { MaybePromiseLike } from "@yume-chan/async";
|
||||||
|
import type {
|
||||||
|
AbortSignal,
|
||||||
|
MaybeConsumable,
|
||||||
|
ReadableStream,
|
||||||
|
WritableStream,
|
||||||
|
} from "@yume-chan/stream-extra";
|
||||||
|
import {
|
||||||
|
ConcatBufferStream,
|
||||||
|
ConcatStringStream,
|
||||||
|
TextDecoderStream,
|
||||||
|
} from "@yume-chan/stream-extra";
|
||||||
|
|
||||||
|
import { splitCommand } from "../utils.js";
|
||||||
|
|
||||||
|
export interface AdbNoneProtocolProcess {
|
||||||
|
get stdin(): WritableStream<MaybeConsumable<Uint8Array>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mix of stdout and stderr
|
||||||
|
*/
|
||||||
|
get output(): ReadableStream<Uint8Array>;
|
||||||
|
|
||||||
|
get exited(): Promise<void>;
|
||||||
|
|
||||||
|
kill(): MaybePromiseLike<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AdbNoneProtocolSpawner {
|
||||||
|
readonly #spawn: (
|
||||||
|
command: string[],
|
||||||
|
signal: AbortSignal | undefined,
|
||||||
|
) => Promise<AdbNoneProtocolProcess>;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
spawn: (
|
||||||
|
command: string[],
|
||||||
|
signal: AbortSignal | undefined,
|
||||||
|
) => Promise<AdbNoneProtocolProcess>,
|
||||||
|
) {
|
||||||
|
this.#spawn = spawn;
|
||||||
|
}
|
||||||
|
|
||||||
|
spawn(
|
||||||
|
command: string | string[],
|
||||||
|
signal?: AbortSignal,
|
||||||
|
): Promise<AdbNoneProtocolProcess> {
|
||||||
|
signal?.throwIfAborted();
|
||||||
|
|
||||||
|
if (typeof command === "string") {
|
||||||
|
command = splitCommand(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.#spawn(command, signal);
|
||||||
|
}
|
||||||
|
|
||||||
|
async spawnWait(command: string | string[]): Promise<Uint8Array> {
|
||||||
|
const process = await this.spawn(command);
|
||||||
|
return await process.output.pipeThrough(new ConcatBufferStream());
|
||||||
|
}
|
||||||
|
|
||||||
|
async spawnWaitText(command: string | string[]): Promise<string> {
|
||||||
|
const process = await this.spawn(command);
|
||||||
|
return await process.output
|
||||||
|
.pipeThrough(new TextDecoderStream())
|
||||||
|
.pipeThrough(new ConcatStringStream());
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,3 +0,0 @@
|
||||||
export * from "./none.js";
|
|
||||||
export * from "./shell.js";
|
|
||||||
export * from "./types.js";
|
|
|
@ -1,90 +0,0 @@
|
||||||
import type {
|
|
||||||
AbortSignal,
|
|
||||||
MaybeConsumable,
|
|
||||||
WritableStream,
|
|
||||||
} from "@yume-chan/stream-extra";
|
|
||||||
import { ReadableStream } from "@yume-chan/stream-extra";
|
|
||||||
|
|
||||||
import type { Adb, AdbSocket } from "../../../adb.js";
|
|
||||||
|
|
||||||
import type { AdbSubprocessProtocol } from "./types.js";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The legacy shell
|
|
||||||
*
|
|
||||||
* Features:
|
|
||||||
* * `stderr`: No
|
|
||||||
* * `exit` exit code: No
|
|
||||||
* * `resize`: No
|
|
||||||
*/
|
|
||||||
export class AdbSubprocessNoneProtocol implements AdbSubprocessProtocol {
|
|
||||||
static isSupported() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static async pty(adb: Adb, command: string, signal?: AbortSignal) {
|
|
||||||
return new AdbSubprocessNoneProtocol(
|
|
||||||
await adb.createSocket(`shell:${command}`),
|
|
||||||
signal,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
static async raw(adb: Adb, command: string, signal?: AbortSignal) {
|
|
||||||
// `shell,raw:${command}` also triggers raw mode,
|
|
||||||
// But is not supported on Android version <7.
|
|
||||||
return new AdbSubprocessNoneProtocol(
|
|
||||||
await adb.createSocket(`exec:${command}`),
|
|
||||||
signal,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
readonly #socket: AdbSocket;
|
|
||||||
|
|
||||||
// Legacy shell forwards all data to stdin.
|
|
||||||
get stdin(): WritableStream<MaybeConsumable<Uint8Array>> {
|
|
||||||
return this.#socket.writable;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Legacy shell mixes stdout and stderr.
|
|
||||||
*/
|
|
||||||
get stdout(): ReadableStream<Uint8Array> {
|
|
||||||
return this.#socket.readable;
|
|
||||||
}
|
|
||||||
|
|
||||||
#stderr: ReadableStream<Uint8Array>;
|
|
||||||
/**
|
|
||||||
* `stderr` will always be empty.
|
|
||||||
*/
|
|
||||||
get stderr(): ReadableStream<Uint8Array> {
|
|
||||||
return this.#stderr;
|
|
||||||
}
|
|
||||||
|
|
||||||
#exit: Promise<number>;
|
|
||||||
get exit() {
|
|
||||||
return this.#exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(socket: AdbSocket, signal?: AbortSignal) {
|
|
||||||
signal?.throwIfAborted();
|
|
||||||
|
|
||||||
this.#socket = socket;
|
|
||||||
signal?.addEventListener("abort", () => void this.kill());
|
|
||||||
|
|
||||||
this.#stderr = new ReadableStream({
|
|
||||||
start: async (controller) => {
|
|
||||||
await this.#socket.closed;
|
|
||||||
controller.close();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
this.#exit = socket.closed.then(() => 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
resize() {
|
|
||||||
// Not supported, but don't throw.
|
|
||||||
}
|
|
||||||
|
|
||||||
async kill() {
|
|
||||||
await this.#socket.close();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,177 +0,0 @@
|
||||||
import { PromiseResolver } from "@yume-chan/async";
|
|
||||||
import type {
|
|
||||||
AbortSignal,
|
|
||||||
PushReadableStreamController,
|
|
||||||
ReadableStream,
|
|
||||||
WritableStreamDefaultWriter,
|
|
||||||
} from "@yume-chan/stream-extra";
|
|
||||||
import {
|
|
||||||
MaybeConsumable,
|
|
||||||
PushReadableStream,
|
|
||||||
StructDeserializeStream,
|
|
||||||
WritableStream,
|
|
||||||
} from "@yume-chan/stream-extra";
|
|
||||||
import type { StructValue } from "@yume-chan/struct";
|
|
||||||
import { buffer, struct, u32, u8 } from "@yume-chan/struct";
|
|
||||||
|
|
||||||
import type { Adb, AdbSocket } from "../../../adb.js";
|
|
||||||
import { AdbFeature } from "../../../features.js";
|
|
||||||
import { encodeUtf8 } from "../../../utils/index.js";
|
|
||||||
|
|
||||||
import type { AdbSubprocessProtocol } from "./types.js";
|
|
||||||
|
|
||||||
export const AdbShellProtocolId = {
|
|
||||||
Stdin: 0,
|
|
||||||
Stdout: 1,
|
|
||||||
Stderr: 2,
|
|
||||||
Exit: 3,
|
|
||||||
CloseStdin: 4,
|
|
||||||
WindowSizeChange: 5,
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
export type AdbShellProtocolId =
|
|
||||||
(typeof AdbShellProtocolId)[keyof typeof AdbShellProtocolId];
|
|
||||||
|
|
||||||
// This packet format is used in both directions.
|
|
||||||
export const AdbShellProtocolPacket = struct(
|
|
||||||
{
|
|
||||||
id: u8<AdbShellProtocolId>(),
|
|
||||||
data: buffer(u32),
|
|
||||||
},
|
|
||||||
{ littleEndian: true },
|
|
||||||
);
|
|
||||||
|
|
||||||
type AdbShellProtocolPacket = StructValue<typeof AdbShellProtocolPacket>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shell v2 a.k.a Shell Protocol
|
|
||||||
*
|
|
||||||
* Features:
|
|
||||||
* * `stderr`: Yes
|
|
||||||
* * `exit` exit code: Yes
|
|
||||||
* * `resize`: Yes
|
|
||||||
*/
|
|
||||||
export class AdbSubprocessShellProtocol implements AdbSubprocessProtocol {
|
|
||||||
static isSupported(adb: Adb) {
|
|
||||||
return adb.canUseFeature(AdbFeature.ShellV2);
|
|
||||||
}
|
|
||||||
|
|
||||||
static async pty(adb: Adb, command: string, signal?: AbortSignal) {
|
|
||||||
// TODO: AdbShellSubprocessProtocol: Support setting `XTERM` environment variable
|
|
||||||
return new AdbSubprocessShellProtocol(
|
|
||||||
await adb.createSocket(`shell,v2,pty:${command}`),
|
|
||||||
signal,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
static async raw(adb: Adb, command: string, signal?: AbortSignal) {
|
|
||||||
return new AdbSubprocessShellProtocol(
|
|
||||||
await adb.createSocket(`shell,v2,raw:${command}`),
|
|
||||||
signal,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
readonly #socket: AdbSocket;
|
|
||||||
#writer: WritableStreamDefaultWriter<MaybeConsumable<Uint8Array>>;
|
|
||||||
|
|
||||||
#stdin: WritableStream<MaybeConsumable<Uint8Array>>;
|
|
||||||
get stdin() {
|
|
||||||
return this.#stdin;
|
|
||||||
}
|
|
||||||
|
|
||||||
#stdout: ReadableStream<Uint8Array>;
|
|
||||||
get stdout() {
|
|
||||||
return this.#stdout;
|
|
||||||
}
|
|
||||||
|
|
||||||
#stderr: ReadableStream<Uint8Array>;
|
|
||||||
get stderr() {
|
|
||||||
return this.#stderr;
|
|
||||||
}
|
|
||||||
|
|
||||||
readonly #exit = new PromiseResolver<number>();
|
|
||||||
get exit() {
|
|
||||||
return this.#exit.promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(socket: AdbSocket, signal?: AbortSignal) {
|
|
||||||
signal?.throwIfAborted();
|
|
||||||
|
|
||||||
this.#socket = socket;
|
|
||||||
signal?.addEventListener("abort", () => void this.kill());
|
|
||||||
|
|
||||||
let stdoutController!: PushReadableStreamController<Uint8Array>;
|
|
||||||
let stderrController!: PushReadableStreamController<Uint8Array>;
|
|
||||||
this.#stdout = new PushReadableStream<Uint8Array>((controller) => {
|
|
||||||
stdoutController = controller;
|
|
||||||
});
|
|
||||||
this.#stderr = new PushReadableStream<Uint8Array>((controller) => {
|
|
||||||
stderrController = controller;
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.readable
|
|
||||||
.pipeThrough(new StructDeserializeStream(AdbShellProtocolPacket))
|
|
||||||
.pipeTo(
|
|
||||||
new WritableStream<AdbShellProtocolPacket>({
|
|
||||||
write: async (chunk) => {
|
|
||||||
switch (chunk.id) {
|
|
||||||
case AdbShellProtocolId.Exit:
|
|
||||||
this.#exit.resolve(chunk.data[0]!);
|
|
||||||
break;
|
|
||||||
case AdbShellProtocolId.Stdout:
|
|
||||||
await stdoutController.enqueue(chunk.data);
|
|
||||||
break;
|
|
||||||
case AdbShellProtocolId.Stderr:
|
|
||||||
await stderrController.enqueue(chunk.data);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.then(
|
|
||||||
() => {
|
|
||||||
stdoutController.close();
|
|
||||||
stderrController.close();
|
|
||||||
// If `#exit` has already resolved, this will be a no-op
|
|
||||||
this.#exit.reject(
|
|
||||||
new Error("Socket ended without exit message"),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
(e) => {
|
|
||||||
stdoutController.error(e);
|
|
||||||
stderrController.error(e);
|
|
||||||
// If `#exit` has already resolved, this will be a no-op
|
|
||||||
this.#exit.reject(e);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
this.#writer = this.#socket.writable.getWriter();
|
|
||||||
|
|
||||||
this.#stdin = new MaybeConsumable.WritableStream<Uint8Array>({
|
|
||||||
write: async (chunk) => {
|
|
||||||
await this.#writer.write(
|
|
||||||
AdbShellProtocolPacket.serialize({
|
|
||||||
id: AdbShellProtocolId.Stdin,
|
|
||||||
data: chunk,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async resize(rows: number, cols: number) {
|
|
||||||
await this.#writer.write(
|
|
||||||
AdbShellProtocolPacket.serialize({
|
|
||||||
id: AdbShellProtocolId.WindowSizeChange,
|
|
||||||
// The "correct" format is `${rows}x${cols},${x_pixels}x${y_pixels}`
|
|
||||||
// However, according to https://linux.die.net/man/4/tty_ioctl
|
|
||||||
// `x_pixels` and `y_pixels` are unused, so always sending `0` should be fine.
|
|
||||||
data: encodeUtf8(`${rows}x${cols},0x0\0`),
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
kill() {
|
|
||||||
return this.#socket.close();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,72 +0,0 @@
|
||||||
import type { MaybePromiseLike } from "@yume-chan/async";
|
|
||||||
import type {
|
|
||||||
AbortSignal,
|
|
||||||
MaybeConsumable,
|
|
||||||
ReadableStream,
|
|
||||||
WritableStream,
|
|
||||||
} from "@yume-chan/stream-extra";
|
|
||||||
|
|
||||||
import type { Adb, AdbSocket } from "../../../adb.js";
|
|
||||||
|
|
||||||
export interface AdbSubprocessProtocol {
|
|
||||||
/**
|
|
||||||
* A WritableStream that writes to the `stdin` stream.
|
|
||||||
*/
|
|
||||||
readonly stdin: WritableStream<MaybeConsumable<Uint8Array>>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The `stdout` stream of the process.
|
|
||||||
*/
|
|
||||||
readonly stdout: ReadableStream<Uint8Array>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The `stderr` stream of the process.
|
|
||||||
*
|
|
||||||
* Note: Some `AdbSubprocessProtocol` doesn't separate `stdout` and `stderr`,
|
|
||||||
* All output will be sent to `stdout`.
|
|
||||||
*/
|
|
||||||
readonly stderr: ReadableStream<Uint8Array>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A `Promise` that resolves to the exit code of the process.
|
|
||||||
*
|
|
||||||
* Note: Some `AdbSubprocessProtocol` doesn't support exit code,
|
|
||||||
* They will always resolve it with `0`.
|
|
||||||
*/
|
|
||||||
readonly exit: Promise<number>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resizes the current shell.
|
|
||||||
*
|
|
||||||
* Some `AdbSubprocessProtocol`s may not support resizing
|
|
||||||
* and will ignore calls to this method.
|
|
||||||
*/
|
|
||||||
resize(rows: number, cols: number): MaybePromiseLike<void>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Kills the current process.
|
|
||||||
*/
|
|
||||||
kill(): MaybePromiseLike<void>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AdbSubprocessProtocolConstructor {
|
|
||||||
/** Returns `true` if the `adb` instance supports this shell */
|
|
||||||
isSupported(adb: Adb): MaybePromiseLike<boolean>;
|
|
||||||
|
|
||||||
/** Spawns an executable in PTY (interactive) mode. */
|
|
||||||
pty: (
|
|
||||||
adb: Adb,
|
|
||||||
command: string,
|
|
||||||
signal?: AbortSignal,
|
|
||||||
) => MaybePromiseLike<AdbSubprocessProtocol>;
|
|
||||||
|
|
||||||
/** Spawns an executable and pipe the output. */
|
|
||||||
raw(
|
|
||||||
adb: Adb,
|
|
||||||
command: string,
|
|
||||||
signal?: AbortSignal,
|
|
||||||
): MaybePromiseLike<AdbSubprocessProtocol>;
|
|
||||||
|
|
||||||
/** Creates a new `AdbShell` by attaching to an exist `AdbSocket` */
|
|
||||||
new (socket: AdbSocket): AdbSubprocessProtocol;
|
|
||||||
}
|
|
15
libraries/adb/src/commands/subprocess/pty.ts
Normal file
15
libraries/adb/src/commands/subprocess/pty.ts
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import type { MaybePromiseLike } from "@yume-chan/async";
|
||||||
|
import type {
|
||||||
|
MaybeConsumable,
|
||||||
|
ReadableStream,
|
||||||
|
WritableStream,
|
||||||
|
} from "@yume-chan/stream-extra";
|
||||||
|
|
||||||
|
export interface AdbPtyProcess<TExitCode> {
|
||||||
|
get input(): WritableStream<MaybeConsumable<Uint8Array>>;
|
||||||
|
get output(): ReadableStream<Uint8Array>;
|
||||||
|
get exited(): Promise<TExitCode>;
|
||||||
|
|
||||||
|
sigint(): Promise<void>;
|
||||||
|
kill(): MaybePromiseLike<void>;
|
||||||
|
}
|
32
libraries/adb/src/commands/subprocess/service.ts
Normal file
32
libraries/adb/src/commands/subprocess/service.ts
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
import type { Adb } from "../../adb.js";
|
||||||
|
import { AdbFeature } from "../../features.js";
|
||||||
|
|
||||||
|
import { AdbNoneProtocolSubprocessService } from "./none/index.js";
|
||||||
|
import { AdbShellProtocolSubprocessService } from "./shell/index.js";
|
||||||
|
|
||||||
|
export class AdbSubprocessService {
|
||||||
|
#adb: Adb;
|
||||||
|
get adb() {
|
||||||
|
return this.#adb;
|
||||||
|
}
|
||||||
|
|
||||||
|
#noneProtocol: AdbNoneProtocolSubprocessService;
|
||||||
|
get noneProtocol(): AdbNoneProtocolSubprocessService {
|
||||||
|
return this.#noneProtocol;
|
||||||
|
}
|
||||||
|
|
||||||
|
#shellProtocol?: AdbShellProtocolSubprocessService;
|
||||||
|
get shellProtocol(): AdbShellProtocolSubprocessService | undefined {
|
||||||
|
return this.#shellProtocol;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(adb: Adb) {
|
||||||
|
this.#adb = adb;
|
||||||
|
|
||||||
|
this.#noneProtocol = new AdbNoneProtocolSubprocessService(adb);
|
||||||
|
|
||||||
|
if (adb.canUseFeature(AdbFeature.ShellV2)) {
|
||||||
|
this.#shellProtocol = new AdbShellProtocolSubprocessService(adb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
5
libraries/adb/src/commands/subprocess/shell/index.ts
Normal file
5
libraries/adb/src/commands/subprocess/shell/index.ts
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
export * from "./process.js";
|
||||||
|
export * from "./pty.js";
|
||||||
|
export * from "./service.js";
|
||||||
|
export * from "./shared.js";
|
||||||
|
export * from "./spawner.js";
|
|
@ -7,16 +7,13 @@ import { ReadableStream, WritableStream } from "@yume-chan/stream-extra";
|
||||||
|
|
||||||
import type { AdbSocket } from "../../../adb.js";
|
import type { AdbSocket } from "../../../adb.js";
|
||||||
|
|
||||||
import {
|
import { AdbShellProtocolProcessImpl } from "./process.js";
|
||||||
AdbShellProtocolId,
|
import { AdbShellProtocolId, AdbShellProtocolPacket } from "./shared.js";
|
||||||
AdbShellProtocolPacket,
|
|
||||||
AdbSubprocessShellProtocol,
|
|
||||||
} from "./shell.js";
|
|
||||||
|
|
||||||
function createMockSocket(
|
function createMockSocket(
|
||||||
readable: (controller: ReadableStreamDefaultController<Uint8Array>) => void,
|
readable: (controller: ReadableStreamDefaultController<Uint8Array>) => void,
|
||||||
): [AdbSocket, PromiseResolver<void>] {
|
): [AdbSocket, PromiseResolver<undefined>] {
|
||||||
const closed = new PromiseResolver<void>();
|
const closed = new PromiseResolver<undefined>();
|
||||||
const socket: AdbSocket = {
|
const socket: AdbSocket = {
|
||||||
service: "",
|
service: "",
|
||||||
close() {},
|
close() {},
|
||||||
|
@ -51,24 +48,12 @@ async function assertResolves<T>(promise: Promise<T>, expected: T) {
|
||||||
return assert.deepStrictEqual(await promise, expected);
|
return assert.deepStrictEqual(await promise, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
describe("AdbShellProtocolPacket", () => {
|
describe("AdbShellProtocolProcessImpl", () => {
|
||||||
it("should serialize", () => {
|
|
||||||
assert.deepStrictEqual(
|
|
||||||
AdbShellProtocolPacket.serialize({
|
|
||||||
id: AdbShellProtocolId.Stdout,
|
|
||||||
data: new Uint8Array([1, 2, 3, 4]),
|
|
||||||
}),
|
|
||||||
new Uint8Array([1, 4, 0, 0, 0, 1, 2, 3, 4]),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("AdbSubprocessShellProtocol", () => {
|
|
||||||
describe("`stdout` and `stderr`", () => {
|
describe("`stdout` and `stderr`", () => {
|
||||||
it("should parse data from `socket", () => {
|
it("should parse data from `socket", () => {
|
||||||
const [socket] = createMockSocket(() => {});
|
const [socket] = createMockSocket(() => {});
|
||||||
|
|
||||||
const process = new AdbSubprocessShellProtocol(socket);
|
const process = new AdbShellProtocolProcessImpl(socket);
|
||||||
const stdoutReader = process.stdout.getReader();
|
const stdoutReader = process.stdout.getReader();
|
||||||
const stderrReader = process.stderr.getReader();
|
const stderrReader = process.stderr.getReader();
|
||||||
|
|
||||||
|
@ -98,12 +83,12 @@ describe("AdbSubprocessShellProtocol", () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const process = new AdbSubprocessShellProtocol(socket);
|
const process = new AdbShellProtocolProcessImpl(socket);
|
||||||
const stdoutReader = process.stdout.getReader();
|
const stdoutReader = process.stdout.getReader();
|
||||||
const stderrReader = process.stderr.getReader();
|
const stderrReader = process.stderr.getReader();
|
||||||
|
|
||||||
await stdoutReader.cancel();
|
await stdoutReader.cancel();
|
||||||
closed.resolve();
|
closed.resolve(undefined);
|
||||||
|
|
||||||
assertResolves(stderrReader.read(), {
|
assertResolves(stderrReader.read(), {
|
||||||
done: false,
|
done: false,
|
||||||
|
@ -118,7 +103,7 @@ describe("AdbSubprocessShellProtocol", () => {
|
||||||
|
|
||||||
describe("`socket` close", () => {
|
describe("`socket` close", () => {
|
||||||
describe("with `exit` message", () => {
|
describe("with `exit` message", () => {
|
||||||
it("should close `stdout`, `stderr` and resolve `exit`", async () => {
|
it("should close `stdout`, `stderr` and resolve `exited`", async () => {
|
||||||
const [socket, closed] = createMockSocket((controller) => {
|
const [socket, closed] = createMockSocket((controller) => {
|
||||||
controller.enqueue(
|
controller.enqueue(
|
||||||
AdbShellProtocolPacket.serialize({
|
AdbShellProtocolPacket.serialize({
|
||||||
|
@ -129,7 +114,7 @@ describe("AdbSubprocessShellProtocol", () => {
|
||||||
controller.close();
|
controller.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
const process = new AdbSubprocessShellProtocol(socket);
|
const process = new AdbShellProtocolProcessImpl(socket);
|
||||||
const stdoutReader = process.stdout.getReader();
|
const stdoutReader = process.stdout.getReader();
|
||||||
const stderrReader = process.stderr.getReader();
|
const stderrReader = process.stderr.getReader();
|
||||||
|
|
||||||
|
@ -142,7 +127,7 @@ describe("AdbSubprocessShellProtocol", () => {
|
||||||
value: new Uint8Array([4, 5, 6]),
|
value: new Uint8Array([4, 5, 6]),
|
||||||
});
|
});
|
||||||
|
|
||||||
closed.resolve();
|
closed.resolve(undefined);
|
||||||
|
|
||||||
assertResolves(stdoutReader.read(), {
|
assertResolves(stdoutReader.read(), {
|
||||||
done: true,
|
done: true,
|
||||||
|
@ -152,17 +137,17 @@ describe("AdbSubprocessShellProtocol", () => {
|
||||||
done: true,
|
done: true,
|
||||||
value: undefined,
|
value: undefined,
|
||||||
});
|
});
|
||||||
assert.strictEqual(await process.exit, 42);
|
assert.strictEqual(await process.exited, 42);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("with no `exit` message", () => {
|
describe("with no `exit` message", () => {
|
||||||
it("should close `stdout`, `stderr` and reject `exit`", async () => {
|
it("should close `stdout`, `stderr` and reject `exited`", async () => {
|
||||||
const [socket, closed] = createMockSocket((controller) => {
|
const [socket, closed] = createMockSocket((controller) => {
|
||||||
controller.close();
|
controller.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
const process = new AdbSubprocessShellProtocol(socket);
|
const process = new AdbShellProtocolProcessImpl(socket);
|
||||||
const stdoutReader = process.stdout.getReader();
|
const stdoutReader = process.stdout.getReader();
|
||||||
const stderrReader = process.stderr.getReader();
|
const stderrReader = process.stderr.getReader();
|
||||||
|
|
||||||
|
@ -175,7 +160,7 @@ describe("AdbSubprocessShellProtocol", () => {
|
||||||
value: new Uint8Array([4, 5, 6]),
|
value: new Uint8Array([4, 5, 6]),
|
||||||
});
|
});
|
||||||
|
|
||||||
closed.resolve();
|
closed.resolve(undefined);
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
assertResolves(stdoutReader.read(), {
|
assertResolves(stdoutReader.read(), {
|
||||||
|
@ -186,20 +171,20 @@ describe("AdbSubprocessShellProtocol", () => {
|
||||||
done: true,
|
done: true,
|
||||||
value: undefined,
|
value: undefined,
|
||||||
}),
|
}),
|
||||||
assert.rejects(process.exit),
|
assert.rejects(process.exited),
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("`socket.readable` invalid data", () => {
|
describe("`socket.readable` invalid data", () => {
|
||||||
it("should error `stdout`, `stderr` and reject `exit`", async () => {
|
it("should error `stdout`, `stderr` and reject `exited`", async () => {
|
||||||
const [socket, closed] = createMockSocket((controller) => {
|
const [socket, closed] = createMockSocket((controller) => {
|
||||||
controller.enqueue(new Uint8Array([7, 8, 9]));
|
controller.enqueue(new Uint8Array([7, 8, 9]));
|
||||||
controller.close();
|
controller.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
const process = new AdbSubprocessShellProtocol(socket);
|
const process = new AdbShellProtocolProcessImpl(socket);
|
||||||
const stdoutReader = process.stdout.getReader();
|
const stdoutReader = process.stdout.getReader();
|
||||||
const stderrReader = process.stderr.getReader();
|
const stderrReader = process.stderr.getReader();
|
||||||
|
|
||||||
|
@ -212,12 +197,12 @@ describe("AdbSubprocessShellProtocol", () => {
|
||||||
value: new Uint8Array([4, 5, 6]),
|
value: new Uint8Array([4, 5, 6]),
|
||||||
});
|
});
|
||||||
|
|
||||||
closed.resolve();
|
closed.resolve(undefined);
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
assert.rejects(stdoutReader.read()),
|
assert.rejects(stdoutReader.read()),
|
||||||
assert.rejects(stderrReader.read()),
|
assert.rejects(stderrReader.read()),
|
||||||
assert.rejects(process.exit),
|
assert.rejects(process.exited),
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
});
|
});
|
127
libraries/adb/src/commands/subprocess/shell/process.ts
Normal file
127
libraries/adb/src/commands/subprocess/shell/process.ts
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
import type { MaybePromiseLike } from "@yume-chan/async";
|
||||||
|
import { PromiseResolver } from "@yume-chan/async";
|
||||||
|
import type {
|
||||||
|
AbortSignal,
|
||||||
|
PushReadableStreamController,
|
||||||
|
ReadableStream,
|
||||||
|
WritableStreamDefaultWriter,
|
||||||
|
} from "@yume-chan/stream-extra";
|
||||||
|
import {
|
||||||
|
MaybeConsumable,
|
||||||
|
PushReadableStream,
|
||||||
|
StructDeserializeStream,
|
||||||
|
WritableStream,
|
||||||
|
} from "@yume-chan/stream-extra";
|
||||||
|
|
||||||
|
import type { AdbSocket } from "../../../adb.js";
|
||||||
|
|
||||||
|
import { AdbShellProtocolId, AdbShellProtocolPacket } from "./shared.js";
|
||||||
|
import type { AdbShellProtocolProcess } from "./spawner.js";
|
||||||
|
|
||||||
|
export class AdbShellProtocolProcessImpl implements AdbShellProtocolProcess {
|
||||||
|
readonly #socket: AdbSocket;
|
||||||
|
readonly #writer: WritableStreamDefaultWriter<MaybeConsumable<Uint8Array>>;
|
||||||
|
|
||||||
|
readonly #stdin: WritableStream<MaybeConsumable<Uint8Array>>;
|
||||||
|
get stdin() {
|
||||||
|
return this.#stdin;
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly #stdout: ReadableStream<Uint8Array>;
|
||||||
|
get stdout() {
|
||||||
|
return this.#stdout;
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly #stderr: ReadableStream<Uint8Array>;
|
||||||
|
get stderr() {
|
||||||
|
return this.#stderr;
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly #exited: Promise<number>;
|
||||||
|
get exited() {
|
||||||
|
return this.#exited;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(socket: AdbSocket, signal?: AbortSignal) {
|
||||||
|
this.#socket = socket;
|
||||||
|
|
||||||
|
let stdoutController!: PushReadableStreamController<Uint8Array>;
|
||||||
|
let stderrController!: PushReadableStreamController<Uint8Array>;
|
||||||
|
this.#stdout = new PushReadableStream<Uint8Array>((controller) => {
|
||||||
|
stdoutController = controller;
|
||||||
|
});
|
||||||
|
this.#stderr = new PushReadableStream<Uint8Array>((controller) => {
|
||||||
|
stderrController = controller;
|
||||||
|
});
|
||||||
|
|
||||||
|
const exited = new PromiseResolver<number>();
|
||||||
|
this.#exited = exited.promise;
|
||||||
|
|
||||||
|
socket.readable
|
||||||
|
.pipeThrough(new StructDeserializeStream(AdbShellProtocolPacket))
|
||||||
|
.pipeTo(
|
||||||
|
new WritableStream<AdbShellProtocolPacket>({
|
||||||
|
write: async (chunk) => {
|
||||||
|
switch (chunk.id) {
|
||||||
|
case AdbShellProtocolId.Exit:
|
||||||
|
exited.resolve(chunk.data[0]!);
|
||||||
|
break;
|
||||||
|
case AdbShellProtocolId.Stdout:
|
||||||
|
await stdoutController.enqueue(chunk.data);
|
||||||
|
break;
|
||||||
|
case AdbShellProtocolId.Stderr:
|
||||||
|
await stderrController.enqueue(chunk.data);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// Ignore unknown messages like Google ADB does
|
||||||
|
// https://cs.android.com/android/platform/superproject/main/+/main:packages/modules/adb/daemon/shell_service.cpp;l=684;drc=61197364367c9e404c7da6900658f1b16c42d0da
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.then(
|
||||||
|
() => {
|
||||||
|
stdoutController.close();
|
||||||
|
stderrController.close();
|
||||||
|
// If `exited` has already settled, this will be a no-op
|
||||||
|
exited.reject(
|
||||||
|
new Error("Socket ended without exit message"),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
(e) => {
|
||||||
|
stdoutController.error(e);
|
||||||
|
stderrController.error(e);
|
||||||
|
// If `exited` has already settled, this will be a no-op
|
||||||
|
exited.reject(e);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (signal) {
|
||||||
|
// `signal` won't affect `this.stdout` and `this.stderr`
|
||||||
|
// So remaining data can still be read
|
||||||
|
// (call `controller.error` will discard all pending data)
|
||||||
|
|
||||||
|
signal.addEventListener("abort", () => {
|
||||||
|
exited.reject(signal.reason);
|
||||||
|
this.#socket.close();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.#writer = this.#socket.writable.getWriter();
|
||||||
|
this.#stdin = new MaybeConsumable.WritableStream<Uint8Array>({
|
||||||
|
write: async (chunk) => {
|
||||||
|
await this.#writer.write(
|
||||||
|
AdbShellProtocolPacket.serialize({
|
||||||
|
id: AdbShellProtocolId.Stdin,
|
||||||
|
data: chunk,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
kill(): MaybePromiseLike<void> {
|
||||||
|
return this.#socket.close();
|
||||||
|
}
|
||||||
|
}
|
112
libraries/adb/src/commands/subprocess/shell/pty.ts
Normal file
112
libraries/adb/src/commands/subprocess/shell/pty.ts
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
import { PromiseResolver } from "@yume-chan/async";
|
||||||
|
import type {
|
||||||
|
PushReadableStreamController,
|
||||||
|
ReadableStream,
|
||||||
|
WritableStreamDefaultWriter,
|
||||||
|
} from "@yume-chan/stream-extra";
|
||||||
|
import {
|
||||||
|
MaybeConsumable,
|
||||||
|
PushReadableStream,
|
||||||
|
StructDeserializeStream,
|
||||||
|
WritableStream,
|
||||||
|
} from "@yume-chan/stream-extra";
|
||||||
|
import { encodeUtf8 } from "@yume-chan/struct";
|
||||||
|
|
||||||
|
import type { AdbSocket } from "../../../adb.js";
|
||||||
|
import type { AdbPtyProcess } from "../pty.js";
|
||||||
|
|
||||||
|
import { AdbShellProtocolId, AdbShellProtocolPacket } from "./shared.js";
|
||||||
|
|
||||||
|
export class AdbShellProtocolPtyProcess implements AdbPtyProcess<number> {
|
||||||
|
readonly #socket: AdbSocket;
|
||||||
|
readonly #writer: WritableStreamDefaultWriter<MaybeConsumable<Uint8Array>>;
|
||||||
|
|
||||||
|
readonly #input: WritableStream<MaybeConsumable<Uint8Array>>;
|
||||||
|
get input() {
|
||||||
|
return this.#input;
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly #stdout: ReadableStream<Uint8Array>;
|
||||||
|
get output() {
|
||||||
|
return this.#stdout;
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly #exited = new PromiseResolver<number>();
|
||||||
|
get exited() {
|
||||||
|
return this.#exited.promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(socket: AdbSocket) {
|
||||||
|
this.#socket = socket;
|
||||||
|
|
||||||
|
let stdoutController!: PushReadableStreamController<Uint8Array>;
|
||||||
|
this.#stdout = new PushReadableStream<Uint8Array>((controller) => {
|
||||||
|
stdoutController = controller;
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.readable
|
||||||
|
.pipeThrough(new StructDeserializeStream(AdbShellProtocolPacket))
|
||||||
|
.pipeTo(
|
||||||
|
new WritableStream<AdbShellProtocolPacket>({
|
||||||
|
write: async (chunk) => {
|
||||||
|
switch (chunk.id) {
|
||||||
|
case AdbShellProtocolId.Exit:
|
||||||
|
this.#exited.resolve(chunk.data[0]!);
|
||||||
|
break;
|
||||||
|
case AdbShellProtocolId.Stdout:
|
||||||
|
await stdoutController.enqueue(chunk.data);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.then(
|
||||||
|
() => {
|
||||||
|
stdoutController.close();
|
||||||
|
// If `#exit` has already resolved, this will be a no-op
|
||||||
|
this.#exited.reject(
|
||||||
|
new Error("Socket ended without exit message"),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
(e) => {
|
||||||
|
stdoutController.error(e);
|
||||||
|
// If `#exit` has already resolved, this will be a no-op
|
||||||
|
this.#exited.reject(e);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
this.#writer = this.#socket.writable.getWriter();
|
||||||
|
this.#input = new MaybeConsumable.WritableStream<Uint8Array>({
|
||||||
|
write: (chunk) => this.#writeStdin(chunk),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#writeStdin(chunk: Uint8Array) {
|
||||||
|
return this.#writer.write(
|
||||||
|
AdbShellProtocolPacket.serialize({
|
||||||
|
id: AdbShellProtocolId.Stdin,
|
||||||
|
data: chunk,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async resize(rows: number, cols: number) {
|
||||||
|
await this.#writer.write(
|
||||||
|
AdbShellProtocolPacket.serialize({
|
||||||
|
id: AdbShellProtocolId.WindowSizeChange,
|
||||||
|
// The "correct" format is `${rows}x${cols},${x_pixels}x${y_pixels}`
|
||||||
|
// However, according to https://linux.die.net/man/4/tty_ioctl
|
||||||
|
// `x_pixels` and `y_pixels` are unused, so always sending `0` should be fine.
|
||||||
|
data: encodeUtf8(`${rows}x${cols},0x0\0`),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
sigint() {
|
||||||
|
return this.#writeStdin(new Uint8Array([0x03]));
|
||||||
|
}
|
||||||
|
|
||||||
|
kill() {
|
||||||
|
return this.#socket.close();
|
||||||
|
}
|
||||||
|
}
|
58
libraries/adb/src/commands/subprocess/shell/service.ts
Normal file
58
libraries/adb/src/commands/subprocess/shell/service.ts
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
import type { Adb } from "../../../adb.js";
|
||||||
|
import { AdbFeature } from "../../../features.js";
|
||||||
|
|
||||||
|
import { AdbShellProtocolProcessImpl } from "./process.js";
|
||||||
|
import { AdbShellProtocolPtyProcess } from "./pty.js";
|
||||||
|
import { AdbShellProtocolSpawner } from "./spawner.js";
|
||||||
|
|
||||||
|
export class AdbShellProtocolSubprocessService extends AdbShellProtocolSpawner {
|
||||||
|
readonly #adb: Adb;
|
||||||
|
get adb() {
|
||||||
|
return this.#adb;
|
||||||
|
}
|
||||||
|
|
||||||
|
get isSupported() {
|
||||||
|
return this.#adb.canUseFeature(AdbFeature.ShellV2);
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(adb: Adb) {
|
||||||
|
super(async (command, signal) => {
|
||||||
|
const socket = await this.#adb.createSocket(
|
||||||
|
`shell,v2,raw:${command.join(" ")}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (signal?.aborted) {
|
||||||
|
await socket.close();
|
||||||
|
throw signal.reason;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new AdbShellProtocolProcessImpl(socket, signal);
|
||||||
|
});
|
||||||
|
this.#adb = adb;
|
||||||
|
}
|
||||||
|
|
||||||
|
async pty(options?: {
|
||||||
|
command?: string | string[] | undefined;
|
||||||
|
terminalType?: string;
|
||||||
|
}): Promise<AdbShellProtocolPtyProcess> {
|
||||||
|
let service = "shell,v2,pty";
|
||||||
|
|
||||||
|
if (options?.terminalType) {
|
||||||
|
service += `,TERM=` + options.terminalType;
|
||||||
|
}
|
||||||
|
|
||||||
|
service += ":";
|
||||||
|
|
||||||
|
if (options) {
|
||||||
|
if (typeof options.command === "string") {
|
||||||
|
service += options.command;
|
||||||
|
} else if (Array.isArray(options.command)) {
|
||||||
|
service += options.command.join(" ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new AdbShellProtocolPtyProcess(
|
||||||
|
await this.#adb.createSocket(service),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
16
libraries/adb/src/commands/subprocess/shell/shared.spec.ts
Normal file
16
libraries/adb/src/commands/subprocess/shell/shared.spec.ts
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
import assert from "node:assert";
|
||||||
|
import { describe, it } from "node:test";
|
||||||
|
|
||||||
|
import { AdbShellProtocolId, AdbShellProtocolPacket } from "./shared.js";
|
||||||
|
|
||||||
|
describe("AdbShellProtocolPacket", () => {
|
||||||
|
it("should serialize", () => {
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
AdbShellProtocolPacket.serialize({
|
||||||
|
id: AdbShellProtocolId.Stdout,
|
||||||
|
data: new Uint8Array([1, 2, 3, 4]),
|
||||||
|
}),
|
||||||
|
new Uint8Array([1, 4, 0, 0, 0, 1, 2, 3, 4]),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
25
libraries/adb/src/commands/subprocess/shell/shared.ts
Normal file
25
libraries/adb/src/commands/subprocess/shell/shared.ts
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import type { StructValue } from "@yume-chan/struct";
|
||||||
|
import { buffer, struct, u32, u8 } from "@yume-chan/struct";
|
||||||
|
|
||||||
|
export const AdbShellProtocolId = {
|
||||||
|
Stdin: 0,
|
||||||
|
Stdout: 1,
|
||||||
|
Stderr: 2,
|
||||||
|
Exit: 3,
|
||||||
|
CloseStdin: 4,
|
||||||
|
WindowSizeChange: 5,
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export type AdbShellProtocolId =
|
||||||
|
(typeof AdbShellProtocolId)[keyof typeof AdbShellProtocolId];
|
||||||
|
|
||||||
|
// This packet format is used in both directions.
|
||||||
|
export const AdbShellProtocolPacket = struct(
|
||||||
|
{
|
||||||
|
id: u8<AdbShellProtocolId>(),
|
||||||
|
data: buffer(u32),
|
||||||
|
},
|
||||||
|
{ littleEndian: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
export type AdbShellProtocolPacket = StructValue<typeof AdbShellProtocolPacket>;
|
90
libraries/adb/src/commands/subprocess/shell/spawner.ts
Normal file
90
libraries/adb/src/commands/subprocess/shell/spawner.ts
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
import type { MaybePromiseLike } from "@yume-chan/async";
|
||||||
|
import type {
|
||||||
|
AbortSignal,
|
||||||
|
MaybeConsumable,
|
||||||
|
ReadableStream,
|
||||||
|
WritableStream,
|
||||||
|
} from "@yume-chan/stream-extra";
|
||||||
|
import {
|
||||||
|
ConcatBufferStream,
|
||||||
|
ConcatStringStream,
|
||||||
|
TextDecoderStream,
|
||||||
|
} from "@yume-chan/stream-extra";
|
||||||
|
|
||||||
|
import { splitCommand } from "../utils.js";
|
||||||
|
|
||||||
|
export interface AdbShellProtocolProcess {
|
||||||
|
get stdin(): WritableStream<MaybeConsumable<Uint8Array>>;
|
||||||
|
|
||||||
|
get stdout(): ReadableStream<Uint8Array>;
|
||||||
|
get stderr(): ReadableStream<Uint8Array>;
|
||||||
|
|
||||||
|
get exited(): Promise<number>;
|
||||||
|
|
||||||
|
kill(): MaybePromiseLike<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AdbShellProtocolSpawner {
|
||||||
|
readonly #spawn: (
|
||||||
|
command: string[],
|
||||||
|
signal: AbortSignal | undefined,
|
||||||
|
) => Promise<AdbShellProtocolProcess>;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
spawn: (
|
||||||
|
command: string[],
|
||||||
|
signal: AbortSignal | undefined,
|
||||||
|
) => Promise<AdbShellProtocolProcess>,
|
||||||
|
) {
|
||||||
|
this.#spawn = spawn;
|
||||||
|
}
|
||||||
|
|
||||||
|
spawn(
|
||||||
|
command: string | string[],
|
||||||
|
signal?: AbortSignal,
|
||||||
|
): Promise<AdbShellProtocolProcess> {
|
||||||
|
signal?.throwIfAborted();
|
||||||
|
|
||||||
|
if (typeof command === "string") {
|
||||||
|
command = splitCommand(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.#spawn(command, signal);
|
||||||
|
}
|
||||||
|
|
||||||
|
async spawnWait(
|
||||||
|
command: string | string[],
|
||||||
|
): Promise<AdbShellProtocolSpawner.WaitResult<Uint8Array>> {
|
||||||
|
const process = await this.spawn(command);
|
||||||
|
const [stdout, stderr, exitCode] = await Promise.all([
|
||||||
|
process.stdout.pipeThrough(new ConcatBufferStream()),
|
||||||
|
process.stderr.pipeThrough(new ConcatBufferStream()),
|
||||||
|
process.exited,
|
||||||
|
]);
|
||||||
|
return { stdout, stderr, exitCode };
|
||||||
|
}
|
||||||
|
|
||||||
|
async spawnWaitText(
|
||||||
|
command: string | string[],
|
||||||
|
): Promise<AdbShellProtocolSpawner.WaitResult<string>> {
|
||||||
|
const process = await this.spawn(command);
|
||||||
|
const [stdout, stderr, exitCode] = await Promise.all([
|
||||||
|
process.stdout
|
||||||
|
.pipeThrough(new TextDecoderStream())
|
||||||
|
.pipeThrough(new ConcatStringStream()),
|
||||||
|
process.stderr
|
||||||
|
.pipeThrough(new TextDecoderStream())
|
||||||
|
.pipeThrough(new ConcatStringStream()),
|
||||||
|
process.exited,
|
||||||
|
]);
|
||||||
|
return { stdout, stderr, exitCode };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace AdbShellProtocolSpawner {
|
||||||
|
export interface WaitResult<T> {
|
||||||
|
stdout: T;
|
||||||
|
stderr: T;
|
||||||
|
exitCode: number;
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,3 +18,44 @@ export function escapeArg(s: string) {
|
||||||
result += `'`;
|
result += `'`;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function splitCommand(command: string): string[] {
|
||||||
|
const result: string[] = [];
|
||||||
|
let quote: string | undefined;
|
||||||
|
let isEscaped = false;
|
||||||
|
let start = 0;
|
||||||
|
|
||||||
|
for (let i = 0, len = command.length; i < len; i += 1) {
|
||||||
|
if (isEscaped) {
|
||||||
|
isEscaped = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char = command.charAt(i);
|
||||||
|
switch (char) {
|
||||||
|
case " ":
|
||||||
|
if (!quote && i !== start) {
|
||||||
|
result.push(command.substring(start, i));
|
||||||
|
start = i + 1;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "'":
|
||||||
|
case '"':
|
||||||
|
if (!quote) {
|
||||||
|
quote = char;
|
||||||
|
} else if (char === quote) {
|
||||||
|
quote = undefined;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "\\":
|
||||||
|
isEscaped = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (start < command.length) {
|
||||||
|
result.push(command.substring(start));
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ describe("AdbSyncSocket", () => {
|
||||||
{
|
{
|
||||||
service: "",
|
service: "",
|
||||||
close() {},
|
close() {},
|
||||||
closed: Promise.resolve(),
|
closed: Promise.resolve(undefined),
|
||||||
readable: new ReadableStream(),
|
readable: new ReadableStream(),
|
||||||
writable: new WritableStream(),
|
writable: new WritableStream(),
|
||||||
},
|
},
|
||||||
|
|
|
@ -151,7 +151,7 @@ export class AdbSync {
|
||||||
// It may fail if `filename` already exists.
|
// It may fail if `filename` already exists.
|
||||||
// Ignore the result.
|
// Ignore the result.
|
||||||
// TODO: sync: test push mkdir workaround (need an Android 8 device)
|
// TODO: sync: test push mkdir workaround (need an Android 8 device)
|
||||||
await this._adb.subprocess.spawnAndWait([
|
await this._adb.subprocess.noneProtocol.spawnWait([
|
||||||
"mkdir",
|
"mkdir",
|
||||||
"-p",
|
"-p",
|
||||||
escapeArg(dirname(options.filename)),
|
escapeArg(dirname(options.filename)),
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { AdbCommandBase } from "./base.js";
|
import { AdbServiceBase } from "./base.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ADB daemon checks for the following properties in the order of
|
* ADB daemon checks for the following properties in the order of
|
||||||
|
@ -27,7 +27,7 @@ function parsePort(value: string): number | undefined {
|
||||||
return Number.parseInt(value, 10);
|
return Number.parseInt(value, 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AdbTcpIpCommand extends AdbCommandBase {
|
export class AdbTcpIpCommand extends AdbServiceBase {
|
||||||
async getListenAddresses(): Promise<AdbTcpIpListenAddresses> {
|
async getListenAddresses(): Promise<AdbTcpIpListenAddresses> {
|
||||||
const serviceListenAddresses = await this.adb.getProp(
|
const serviceListenAddresses = await this.adb.getProp(
|
||||||
"service.adb.listen_addrs",
|
"service.adb.listen_addrs",
|
||||||
|
|
|
@ -54,7 +54,7 @@ export class AdbDaemonSocketController
|
||||||
|
|
||||||
#closed = false;
|
#closed = false;
|
||||||
|
|
||||||
#closedPromise = new PromiseResolver<void>();
|
#closedPromise = new PromiseResolver<undefined>();
|
||||||
get closed() {
|
get closed() {
|
||||||
return this.#closedPromise.promise;
|
return this.#closedPromise.promise;
|
||||||
}
|
}
|
||||||
|
@ -170,7 +170,7 @@ export class AdbDaemonSocketController
|
||||||
|
|
||||||
dispose() {
|
dispose() {
|
||||||
this.#readableController.close();
|
this.#readableController.close();
|
||||||
this.#closedPromise.resolve();
|
this.#closedPromise.resolve(undefined);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -200,7 +200,7 @@ export class AdbDaemonSocket implements AdbDaemonSocketInfo, AdbSocket {
|
||||||
return this.#controller.writable;
|
return this.#controller.writable;
|
||||||
}
|
}
|
||||||
|
|
||||||
get closed(): Promise<void> {
|
get closed(): Promise<undefined> {
|
||||||
return this.#controller.closed;
|
return this.#controller.closed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -458,10 +458,7 @@ export class AdbServerClient {
|
||||||
disconnected,
|
disconnected,
|
||||||
);
|
);
|
||||||
|
|
||||||
transport.disconnected.then(
|
void transport.disconnected.finally(() => waitAbortController.abort());
|
||||||
() => waitAbortController.abort(),
|
|
||||||
() => waitAbortController.abort(),
|
|
||||||
);
|
|
||||||
|
|
||||||
return transport;
|
return transport;
|
||||||
}
|
}
|
||||||
|
@ -507,7 +504,7 @@ export namespace AdbServerClient {
|
||||||
export interface ServerConnection
|
export interface ServerConnection
|
||||||
extends ReadableWritablePair<Uint8Array, MaybeConsumable<Uint8Array>>,
|
extends ReadableWritablePair<Uint8Array, MaybeConsumable<Uint8Array>>,
|
||||||
Closeable {
|
Closeable {
|
||||||
get closed(): Promise<void>;
|
get closed(): Promise<undefined>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ServerConnector {
|
export interface ServerConnector {
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
import type { Adb } from "@yume-chan/adb";
|
import type { Adb } from "@yume-chan/adb";
|
||||||
import { AdbCommandBase } from "@yume-chan/adb";
|
import { AdbServiceBase } from "@yume-chan/adb";
|
||||||
import { ConcatStringStream, TextDecoderStream } from "@yume-chan/stream-extra";
|
|
||||||
|
|
||||||
import { Cmd } from "./cmd.js";
|
import { CmdNoneProtocolService } from "./cmd.js";
|
||||||
import type { IntentBuilder } from "./intent.js";
|
import type { IntentBuilder } from "./intent.js";
|
||||||
import type { SingleUser } from "./utils.js";
|
import type { SingleUser } from "./utils.js";
|
||||||
import { buildArguments } from "./utils.js";
|
import { buildArguments } from "./utils.js";
|
||||||
|
@ -24,39 +23,33 @@ const START_ACTIVITY_OPTIONS_MAP: Partial<
|
||||||
user: "--user",
|
user: "--user",
|
||||||
};
|
};
|
||||||
|
|
||||||
export class ActivityManager extends AdbCommandBase {
|
export class ActivityManager extends AdbServiceBase {
|
||||||
#cmd: Cmd;
|
static ServiceName = "activity";
|
||||||
|
static CommandName = "am";
|
||||||
|
|
||||||
|
#cmd: CmdNoneProtocolService;
|
||||||
|
|
||||||
constructor(adb: Adb) {
|
constructor(adb: Adb) {
|
||||||
super(adb);
|
super(adb);
|
||||||
this.#cmd = new Cmd(adb);
|
this.#cmd = new CmdNoneProtocolService(
|
||||||
}
|
adb,
|
||||||
|
ActivityManager.CommandName,
|
||||||
async #cmdOrSubprocess(args: string[]) {
|
);
|
||||||
if (this.#cmd.supportsCmd) {
|
|
||||||
args.shift();
|
|
||||||
return await this.#cmd.spawn(false, "activity", ...args);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.adb.subprocess.spawn(args);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async startActivity(
|
async startActivity(
|
||||||
options: ActivityManagerStartActivityOptions,
|
options: ActivityManagerStartActivityOptions,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
let args = buildArguments(
|
let args = buildArguments(
|
||||||
["am", "start-activity", "-W"],
|
[ActivityManager.ServiceName, "start-activity", "-W"],
|
||||||
options,
|
options,
|
||||||
START_ACTIVITY_OPTIONS_MAP,
|
START_ACTIVITY_OPTIONS_MAP,
|
||||||
);
|
);
|
||||||
|
|
||||||
args = args.concat(options.intent.build());
|
args = args.concat(options.intent.build());
|
||||||
|
|
||||||
const process = await this.#cmdOrSubprocess(args);
|
const output = await this.#cmd
|
||||||
|
.spawnWaitText(args)
|
||||||
const output = await process.stdout
|
|
||||||
.pipeThrough(new TextDecoderStream())
|
|
||||||
.pipeThrough(new ConcatStringStream())
|
|
||||||
.then((output) => output.trim());
|
.then((output) => output.trim());
|
||||||
|
|
||||||
for (const line of output) {
|
for (const line of output) {
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import { AdbCommandBase } from "@yume-chan/adb";
|
import { AdbServiceBase } from "@yume-chan/adb";
|
||||||
import type { MaybeConsumable, ReadableStream } from "@yume-chan/stream-extra";
|
import type { MaybeConsumable, ReadableStream } from "@yume-chan/stream-extra";
|
||||||
import { ConcatStringStream, TextDecoderStream } from "@yume-chan/stream-extra";
|
|
||||||
|
|
||||||
export interface AdbBackupOptions {
|
export interface AdbBackupOptions {
|
||||||
user: number;
|
user: number;
|
||||||
|
@ -18,7 +17,7 @@ export interface AdbRestoreOptions {
|
||||||
file: ReadableStream<MaybeConsumable<Uint8Array>>;
|
file: ReadableStream<MaybeConsumable<Uint8Array>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AdbBackup extends AdbCommandBase {
|
export class AdbBackup extends AdbServiceBase {
|
||||||
/**
|
/**
|
||||||
* User must confirm backup on device within 60 seconds.
|
* User must confirm backup on device within 60 seconds.
|
||||||
*/
|
*/
|
||||||
|
@ -55,26 +54,19 @@ export class AdbBackup extends AdbCommandBase {
|
||||||
args.push(...options.packages);
|
args.push(...options.packages);
|
||||||
}
|
}
|
||||||
|
|
||||||
const process = await this.adb.subprocess.spawn(args);
|
const process = await this.adb.subprocess.noneProtocol.spawn(args);
|
||||||
return process.stdout;
|
return process.output;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* User must enter the password (if any) and
|
* User must enter the password (if any) and
|
||||||
* confirm restore on device within 60 seconds.
|
* confirm restore on device within 60 seconds.
|
||||||
*/
|
*/
|
||||||
async restore(options: AdbRestoreOptions): Promise<string> {
|
restore(options: AdbRestoreOptions): Promise<string> {
|
||||||
const args = ["bu", "restore"];
|
const args = ["bu", "restore"];
|
||||||
if (options.user !== undefined) {
|
if (options.user !== undefined) {
|
||||||
args.push("--user", options.user.toString());
|
args.push("--user", options.user.toString());
|
||||||
}
|
}
|
||||||
const process = await this.adb.subprocess.spawn(args);
|
return this.adb.subprocess.noneProtocol.spawnWaitText(args);
|
||||||
const [output] = await Promise.all([
|
|
||||||
process.stdout
|
|
||||||
.pipeThrough(new TextDecoderStream())
|
|
||||||
.pipeThrough(new ConcatStringStream()),
|
|
||||||
options.file.pipeTo(process.stdin),
|
|
||||||
]);
|
|
||||||
return output;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
// cspell: ignore bugreportz
|
// cspell: ignore bugreportz
|
||||||
|
|
||||||
import type { Adb, AdbSync } from "@yume-chan/adb";
|
import type { Adb, AdbSync } from "@yume-chan/adb";
|
||||||
import { AdbCommandBase, AdbSubprocessShellProtocol } from "@yume-chan/adb";
|
import { AdbServiceBase } from "@yume-chan/adb";
|
||||||
import type { AbortSignal, ReadableStream } from "@yume-chan/stream-extra";
|
import type { AbortSignal, ReadableStream } from "@yume-chan/stream-extra";
|
||||||
import {
|
import {
|
||||||
AbortController,
|
AbortController,
|
||||||
|
@ -31,7 +31,7 @@ export interface BugReportZOptions {
|
||||||
onProgress?: ((completed: string, total: string) => void) | undefined;
|
onProgress?: ((completed: string, total: string) => void) | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class BugReport extends AdbCommandBase {
|
export class BugReport extends AdbServiceBase {
|
||||||
static VERSION_REGEX: RegExp = /(\d+)\.(\d+)/;
|
static VERSION_REGEX: RegExp = /(\d+)\.(\d+)/;
|
||||||
|
|
||||||
static BEGIN_REGEX: RegExp = /BEGIN:(.*)/;
|
static BEGIN_REGEX: RegExp = /BEGIN:(.*)/;
|
||||||
|
@ -47,7 +47,7 @@ export class BugReport extends AdbCommandBase {
|
||||||
*/
|
*/
|
||||||
static async queryCapabilities(adb: Adb): Promise<BugReport> {
|
static async queryCapabilities(adb: Adb): Promise<BugReport> {
|
||||||
// bugreportz requires shell protocol
|
// bugreportz requires shell protocol
|
||||||
if (!AdbSubprocessShellProtocol.isSupported(adb)) {
|
if (!adb.subprocess.shellProtocol) {
|
||||||
return new BugReport(adb, {
|
return new BugReport(adb, {
|
||||||
supportsBugReport: true,
|
supportsBugReport: true,
|
||||||
bugReportZVersion: undefined,
|
bugReportZVersion: undefined,
|
||||||
|
@ -57,11 +57,11 @@ export class BugReport extends AdbCommandBase {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const { stderr, exitCode } = await adb.subprocess.spawnAndWait([
|
const result = await adb.subprocess.shellProtocol.spawnWaitText([
|
||||||
"bugreportz",
|
"bugreportz",
|
||||||
"-v",
|
"-v",
|
||||||
]);
|
]);
|
||||||
if (exitCode !== 0 || stderr === "") {
|
if (result.exitCode !== 0 || result.stderr === "") {
|
||||||
return new BugReport(adb, {
|
return new BugReport(adb, {
|
||||||
supportsBugReport: true,
|
supportsBugReport: true,
|
||||||
bugReportZVersion: undefined,
|
bugReportZVersion: undefined,
|
||||||
|
@ -71,7 +71,7 @@ export class BugReport extends AdbCommandBase {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const match = stderr.match(BugReport.VERSION_REGEX);
|
const match = result.stderr.match(BugReport.VERSION_REGEX);
|
||||||
if (!match) {
|
if (!match) {
|
||||||
return new BugReport(adb, {
|
return new BugReport(adb, {
|
||||||
supportsBugReport: true,
|
supportsBugReport: true,
|
||||||
|
@ -170,8 +170,9 @@ export class BugReport extends AdbCommandBase {
|
||||||
|
|
||||||
return new WrapReadableStream(async () => {
|
return new WrapReadableStream(async () => {
|
||||||
// https://cs.android.com/android/platform/superproject/+/master:frameworks/native/cmds/bugreport/bugreport.cpp;drc=9b73bf07d73dbab5b792632e1e233edbad77f5fd;bpv=0;bpt=0
|
// https://cs.android.com/android/platform/superproject/+/master:frameworks/native/cmds/bugreport/bugreport.cpp;drc=9b73bf07d73dbab5b792632e1e233edbad77f5fd;bpv=0;bpt=0
|
||||||
const process = await this.adb.subprocess.spawn(["bugreport"]);
|
const process =
|
||||||
return process.stdout;
|
await this.adb.subprocess.noneProtocol.spawn("bugreport");
|
||||||
|
return process.output;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -200,9 +201,8 @@ export class BugReport extends AdbCommandBase {
|
||||||
args.push("-p");
|
args.push("-p");
|
||||||
}
|
}
|
||||||
|
|
||||||
const process = await this.adb.subprocess.spawn(args, {
|
// `subprocess.shellProtocol` must be defined when `this.#supportsBugReportZ` is `true`
|
||||||
protocols: [AdbSubprocessShellProtocol],
|
const process = await this.adb.subprocess.shellProtocol!.spawn(args);
|
||||||
});
|
|
||||||
|
|
||||||
options?.signal?.addEventListener("abort", () => {
|
options?.signal?.addEventListener("abort", () => {
|
||||||
void process.kill();
|
void process.kill();
|
||||||
|
@ -258,10 +258,10 @@ export class BugReport extends AdbCommandBase {
|
||||||
*/
|
*/
|
||||||
bugReportZStream(): ReadableStream<Uint8Array> {
|
bugReportZStream(): ReadableStream<Uint8Array> {
|
||||||
return new PushReadableStream(async (controller) => {
|
return new PushReadableStream(async (controller) => {
|
||||||
const process = await this.adb.subprocess.spawn(
|
const process = await this.adb.subprocess.shellProtocol!.spawn([
|
||||||
["bugreportz", "-s"],
|
"bugreportz",
|
||||||
{ protocols: [AdbSubprocessShellProtocol] },
|
"-s",
|
||||||
);
|
]);
|
||||||
process.stdout
|
process.stdout
|
||||||
.pipeTo(
|
.pipeTo(
|
||||||
new WritableStream({
|
new WritableStream({
|
||||||
|
@ -285,7 +285,7 @@ export class BugReport extends AdbCommandBase {
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
controller.error(e);
|
controller.error(e);
|
||||||
});
|
});
|
||||||
await process.exit;
|
await process.exited;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,21 +1,17 @@
|
||||||
import type {
|
import type { Adb, AdbShellProtocolProcess } from "@yume-chan/adb";
|
||||||
Adb,
|
|
||||||
AdbSubprocessProtocol,
|
|
||||||
AdbSubprocessProtocolConstructor,
|
|
||||||
AdbSubprocessWaitResult,
|
|
||||||
} from "@yume-chan/adb";
|
|
||||||
import {
|
import {
|
||||||
AdbCommandBase,
|
|
||||||
AdbFeature,
|
AdbFeature,
|
||||||
AdbSubprocessNoneProtocol,
|
AdbNoneProtocolProcessImpl,
|
||||||
AdbSubprocessShellProtocol,
|
AdbNoneProtocolSpawner,
|
||||||
|
AdbServiceBase,
|
||||||
|
AdbShellProtocolProcessImpl,
|
||||||
|
AdbShellProtocolSpawner,
|
||||||
} from "@yume-chan/adb";
|
} from "@yume-chan/adb";
|
||||||
import { ConcatStringStream, TextDecoderStream } from "@yume-chan/stream-extra";
|
|
||||||
|
|
||||||
export class Cmd extends AdbCommandBase {
|
export class CmdNoneProtocolService extends AdbNoneProtocolSpawner {
|
||||||
#supportsShellV2: boolean;
|
#supportsAbbExec: boolean;
|
||||||
get supportsShellV2(): boolean {
|
get supportsAbbExec(): boolean {
|
||||||
return this.#supportsShellV2;
|
return this.#supportsAbbExec;
|
||||||
}
|
}
|
||||||
|
|
||||||
#supportsCmd: boolean;
|
#supportsCmd: boolean;
|
||||||
|
@ -23,91 +19,146 @@ export class Cmd extends AdbCommandBase {
|
||||||
return this.#supportsCmd;
|
return this.#supportsCmd;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get isSupported() {
|
||||||
|
return this.#supportsAbbExec || this.#supportsCmd;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
adb: Adb,
|
||||||
|
fallback?:
|
||||||
|
| string
|
||||||
|
| Record<string, string>
|
||||||
|
| ((service: string) => string),
|
||||||
|
) {
|
||||||
|
super(async (command) => {
|
||||||
|
if (this.#supportsAbbExec) {
|
||||||
|
return new AdbNoneProtocolProcessImpl(
|
||||||
|
await adb.createSocket(`abb_exec:${command.join("\0")}\0`),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.#supportsCmd) {
|
||||||
|
return adb.subprocess.noneProtocol.spawn(
|
||||||
|
`cmd ${command.join(" ")}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof fallback === "function") {
|
||||||
|
fallback = fallback(command[0]!);
|
||||||
|
} else if (typeof fallback === "object") {
|
||||||
|
fallback = fallback[command[0]!];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fallback) {
|
||||||
|
throw new Error("Unsupported");
|
||||||
|
}
|
||||||
|
|
||||||
|
command[0] = fallback;
|
||||||
|
return adb.subprocess.noneProtocol.spawn(command);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.#supportsCmd = adb.canUseFeature(AdbFeature.Cmd);
|
||||||
|
this.#supportsAbbExec = adb.canUseFeature(AdbFeature.AbbExec);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CmdShellProtocolService extends AdbShellProtocolSpawner {
|
||||||
|
#adb: Adb;
|
||||||
|
|
||||||
|
#supportsCmd: boolean;
|
||||||
|
get supportsCmd(): boolean {
|
||||||
|
return this.#supportsCmd;
|
||||||
|
}
|
||||||
|
|
||||||
#supportsAbb: boolean;
|
#supportsAbb: boolean;
|
||||||
get supportsAbb(): boolean {
|
get supportsAbb(): boolean {
|
||||||
return this.#supportsAbb;
|
return this.#supportsAbb;
|
||||||
}
|
}
|
||||||
|
|
||||||
#supportsAbbExec: boolean;
|
get isSupported() {
|
||||||
get supportsAbbExec(): boolean {
|
return (
|
||||||
return this.#supportsAbbExec;
|
this.#supportsAbb ||
|
||||||
|
(this.#supportsCmd && !!this.#adb.subprocess.shellProtocol)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(adb: Adb) {
|
constructor(
|
||||||
super(adb);
|
adb: Adb,
|
||||||
this.#supportsShellV2 = adb.canUseFeature(AdbFeature.ShellV2);
|
fallback?:
|
||||||
|
| string
|
||||||
|
| Record<string, string>
|
||||||
|
| ((service: string) => string),
|
||||||
|
) {
|
||||||
|
super(async (command): Promise<AdbShellProtocolProcess> => {
|
||||||
|
if (this.#supportsAbb) {
|
||||||
|
return new AdbShellProtocolProcessImpl(
|
||||||
|
await this.#adb.createSocket(`abb:${command.join("\0")}\0`),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!adb.subprocess.shellProtocol) {
|
||||||
|
throw new Error("Unsupported");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.#supportsCmd) {
|
||||||
|
return adb.subprocess.shellProtocol.spawn(
|
||||||
|
`cmd ${command.join(" ")}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof fallback === "function") {
|
||||||
|
fallback = fallback(command[0]!);
|
||||||
|
} else if (typeof fallback === "object") {
|
||||||
|
fallback = fallback[command[0]!];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fallback) {
|
||||||
|
throw new Error("Unsupported");
|
||||||
|
}
|
||||||
|
|
||||||
|
command[0] = fallback;
|
||||||
|
return adb.subprocess.shellProtocol.spawn(command);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.#adb = adb;
|
||||||
this.#supportsCmd = adb.canUseFeature(AdbFeature.Cmd);
|
this.#supportsCmd = adb.canUseFeature(AdbFeature.Cmd);
|
||||||
this.#supportsAbb = adb.canUseFeature(AdbFeature.Abb);
|
this.#supportsAbb = adb.canUseFeature(AdbFeature.Abb);
|
||||||
this.#supportsAbbExec = adb.canUseFeature(AdbFeature.AbbExec);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
export class Cmd extends AdbServiceBase {
|
||||||
* Spawn a new `cmd` command. It will use ADB's `abb` command if available.
|
#noneProtocol: CmdNoneProtocolService | undefined;
|
||||||
*
|
get noneProtocol() {
|
||||||
* @param shellProtocol
|
return this.#noneProtocol;
|
||||||
* Whether to use shell protocol. If `true`, `stdout` and `stderr` will be separated.
|
}
|
||||||
*
|
|
||||||
* `cmd` doesn't use PTY, so even when shell protocol is used,
|
#shellProtocol: CmdShellProtocolService | undefined;
|
||||||
* resizing terminal size and closing `stdin` are not supported.
|
get shellProtocol() {
|
||||||
* @param command The command to run.
|
return this.#shellProtocol;
|
||||||
* @param args The arguments to pass to the command.
|
}
|
||||||
* @returns An `AdbSubprocessProtocol` that provides output streams.
|
|
||||||
*/
|
constructor(
|
||||||
async spawn(
|
adb: Adb,
|
||||||
shellProtocol: boolean,
|
fallback?:
|
||||||
command: string,
|
| string
|
||||||
...args: string[]
|
| Record<string, string>
|
||||||
): Promise<AdbSubprocessProtocol> {
|
| ((service: string) => string),
|
||||||
let supportsAbb: boolean;
|
) {
|
||||||
let supportsCmd: boolean = this.#supportsCmd;
|
super(adb);
|
||||||
let service: string;
|
|
||||||
let Protocol: AdbSubprocessProtocolConstructor;
|
if (
|
||||||
if (shellProtocol) {
|
adb.canUseFeature(AdbFeature.AbbExec) ||
|
||||||
supportsAbb = this.#supportsAbb;
|
adb.canUseFeature(AdbFeature.Cmd)
|
||||||
supportsCmd &&= this.supportsShellV2;
|
) {
|
||||||
service = "abb";
|
this.#noneProtocol = new CmdNoneProtocolService(adb, fallback);
|
||||||
Protocol = AdbSubprocessShellProtocol;
|
}
|
||||||
} else {
|
|
||||||
supportsAbb = this.#supportsAbbExec;
|
if (
|
||||||
service = "abb_exec";
|
adb.canUseFeature(AdbFeature.Abb) ||
|
||||||
Protocol = AdbSubprocessNoneProtocol;
|
(adb.canUseFeature(AdbFeature.Cmd) &&
|
||||||
}
|
adb.canUseFeature(AdbFeature.ShellV2))
|
||||||
|
) {
|
||||||
if (supportsAbb) {
|
this.#shellProtocol = new CmdShellProtocolService(adb, fallback);
|
||||||
return new Protocol(
|
}
|
||||||
await this.adb.createSocket(
|
|
||||||
`${service}:${command}\0${args.join("\0")}\0`,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (supportsCmd) {
|
|
||||||
return Protocol.raw(this.adb, `cmd ${command} ${args.join(" ")}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error("Not supported");
|
|
||||||
}
|
|
||||||
|
|
||||||
async spawnAndWait(
|
|
||||||
command: string,
|
|
||||||
...args: string[]
|
|
||||||
): Promise<AdbSubprocessWaitResult> {
|
|
||||||
const process = await this.spawn(true, command, ...args);
|
|
||||||
|
|
||||||
const [stdout, stderr, exitCode] = await Promise.all([
|
|
||||||
process.stdout
|
|
||||||
.pipeThrough(new TextDecoderStream())
|
|
||||||
.pipeThrough(new ConcatStringStream()),
|
|
||||||
process.stderr
|
|
||||||
.pipeThrough(new TextDecoderStream())
|
|
||||||
.pipeThrough(new ConcatStringStream()),
|
|
||||||
process.exit,
|
|
||||||
]);
|
|
||||||
|
|
||||||
return {
|
|
||||||
stdout,
|
|
||||||
stderr,
|
|
||||||
exitCode,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
// cspell: ignore sysui
|
// cspell: ignore sysui
|
||||||
|
|
||||||
import type { Adb } from "@yume-chan/adb";
|
import type { Adb } from "@yume-chan/adb";
|
||||||
import { AdbCommandBase } from "@yume-chan/adb";
|
import { AdbServiceBase } from "@yume-chan/adb";
|
||||||
|
|
||||||
import { Settings } from "./settings.js";
|
import { Settings } from "./settings.js";
|
||||||
|
|
||||||
|
@ -51,7 +51,7 @@ export const DemoModeStatusBarModes = [
|
||||||
|
|
||||||
export type DemoModeStatusBarMode = (typeof DemoModeStatusBarModes)[number];
|
export type DemoModeStatusBarMode = (typeof DemoModeStatusBarModes)[number];
|
||||||
|
|
||||||
export class DemoMode extends AdbCommandBase {
|
export class DemoMode extends AdbServiceBase {
|
||||||
#settings: Settings;
|
#settings: Settings;
|
||||||
|
|
||||||
constructor(adb: Adb) {
|
constructor(adb: Adb) {
|
||||||
|
@ -112,7 +112,7 @@ export class DemoMode extends AdbCommandBase {
|
||||||
command: string,
|
command: string,
|
||||||
extra?: Record<string, string>,
|
extra?: Record<string, string>,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
await this.adb.subprocess.spawnAndWaitLegacy([
|
await this.adb.subprocess.noneProtocol.spawnWaitText([
|
||||||
"am",
|
"am",
|
||||||
"broadcast",
|
"broadcast",
|
||||||
"-a",
|
"-a",
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { AdbCommandBase } from "@yume-chan/adb";
|
import { AdbServiceBase } from "@yume-chan/adb";
|
||||||
|
|
||||||
const BatteryDumpFields: Record<
|
const BatteryDumpFields: Record<
|
||||||
string,
|
string,
|
||||||
|
@ -54,17 +54,17 @@ const Battery = {
|
||||||
Health,
|
Health,
|
||||||
};
|
};
|
||||||
|
|
||||||
export class DumpSys extends AdbCommandBase {
|
export class DumpSys extends AdbServiceBase {
|
||||||
static readonly Battery = Battery;
|
static readonly Battery = Battery;
|
||||||
|
|
||||||
async diskStats() {
|
async diskStats() {
|
||||||
const output = await this.adb.subprocess.spawnAndWaitLegacy([
|
const result = await this.adb.subprocess.noneProtocol.spawnWaitText([
|
||||||
"dumpsys",
|
"dumpsys",
|
||||||
"diskstats",
|
"diskstats",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
function getSize(name: string) {
|
function getSize(name: string) {
|
||||||
const match = output.match(
|
const match = result.match(
|
||||||
new RegExp(`${name}-Free: (\\d+)K / (\\d+)K`),
|
new RegExp(`${name}-Free: (\\d+)K / (\\d+)K`),
|
||||||
);
|
);
|
||||||
if (!match) {
|
if (!match) {
|
||||||
|
@ -91,7 +91,7 @@ export class DumpSys extends AdbCommandBase {
|
||||||
}
|
}
|
||||||
|
|
||||||
async battery(): Promise<DumpSys.Battery.Info> {
|
async battery(): Promise<DumpSys.Battery.Info> {
|
||||||
const output = await this.adb.subprocess.spawnAndWaitLegacy([
|
const result = await this.adb.subprocess.noneProtocol.spawnWaitText([
|
||||||
"dumpsys",
|
"dumpsys",
|
||||||
"battery",
|
"battery",
|
||||||
]);
|
]);
|
||||||
|
@ -105,7 +105,7 @@ export class DumpSys extends AdbCommandBase {
|
||||||
health: DumpSys.Battery.Health.Unknown,
|
health: DumpSys.Battery.Health.Unknown,
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const line of output.split("\n")) {
|
for (const line of result.split("\n")) {
|
||||||
const parts = line.split(":").map((part) => part.trim());
|
const parts = line.split(":").map((part) => part.trim());
|
||||||
if (parts.length !== 2) {
|
if (parts.length !== 2) {
|
||||||
continue;
|
continue;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// cspell: ignore logcat
|
// cspell: ignore logcat
|
||||||
// cspell: ignore usec
|
// cspell: ignore usec
|
||||||
|
|
||||||
import { AdbCommandBase, AdbSubprocessNoneProtocol } from "@yume-chan/adb";
|
import { AdbServiceBase } from "@yume-chan/adb";
|
||||||
import type { ReadableStream } from "@yume-chan/stream-extra";
|
import type { ReadableStream } from "@yume-chan/stream-extra";
|
||||||
import {
|
import {
|
||||||
BufferedTransformStream,
|
BufferedTransformStream,
|
||||||
|
@ -405,7 +405,7 @@ export interface LogSize {
|
||||||
maxPayloadSize: number;
|
maxPayloadSize: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Logcat extends AdbCommandBase {
|
export class Logcat extends AdbServiceBase {
|
||||||
static logIdToName(id: LogId): string {
|
static logIdToName(id: LogId): string {
|
||||||
return LogIdName[id]!;
|
return LogIdName[id]!;
|
||||||
}
|
}
|
||||||
|
@ -435,14 +435,14 @@ export class Logcat extends AdbCommandBase {
|
||||||
/(.*): ring buffer is (.*) (.*)B \((.*) (.*)B consumed, (.*) (.*)B readable\), max entry is (.*) B, max payload is (.*) B/;
|
/(.*): ring buffer is (.*) (.*)B \((.*) (.*)B consumed, (.*) (.*)B readable\), max entry is (.*) B, max payload is (.*) B/;
|
||||||
|
|
||||||
async getLogSize(ids?: LogId[]): Promise<LogSize[]> {
|
async getLogSize(ids?: LogId[]): Promise<LogSize[]> {
|
||||||
const { stdout } = await this.adb.subprocess.spawn([
|
const process = await this.adb.subprocess.noneProtocol.spawn([
|
||||||
"logcat",
|
"logcat",
|
||||||
"-g",
|
"-g",
|
||||||
...(ids ? ["-b", Logcat.joinLogId(ids)] : []),
|
...(ids ? ["-b", Logcat.joinLogId(ids)] : []),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const result: LogSize[] = [];
|
const result: LogSize[] = [];
|
||||||
for await (const line of stdout
|
for await (const line of process.output
|
||||||
.pipeThrough(new TextDecoderStream())
|
.pipeThrough(new TextDecoderStream())
|
||||||
.pipeThrough(new SplitStringStream("\n"))) {
|
.pipeThrough(new SplitStringStream("\n"))) {
|
||||||
let match = line.match(Logcat.LOG_SIZE_REGEX_11);
|
let match = line.match(Logcat.LOG_SIZE_REGEX_11);
|
||||||
|
@ -494,7 +494,7 @@ export class Logcat extends AdbCommandBase {
|
||||||
args.push("-b", Logcat.joinLogId(ids));
|
args.push("-b", Logcat.joinLogId(ids));
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.adb.subprocess.spawnAndWait(args);
|
await this.adb.subprocess.noneProtocol.spawnWaitText(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
binary(options?: LogcatOptions): ReadableStream<AndroidLogEntry> {
|
binary(options?: LogcatOptions): ReadableStream<AndroidLogEntry> {
|
||||||
|
@ -520,11 +520,8 @@ export class Logcat extends AdbCommandBase {
|
||||||
|
|
||||||
// TODO: make `spawn` return synchronously with streams pending
|
// TODO: make `spawn` return synchronously with streams pending
|
||||||
// so it's easier to chain them.
|
// so it's easier to chain them.
|
||||||
const { stdout } = await this.adb.subprocess.spawn(args, {
|
const process = await this.adb.subprocess.noneProtocol.spawn(args);
|
||||||
// PERF: None protocol is 150% faster then Shell protocol
|
return process.output;
|
||||||
protocols: [AdbSubprocessNoneProtocol],
|
|
||||||
});
|
|
||||||
return stdout;
|
|
||||||
}).pipeThrough(
|
}).pipeThrough(
|
||||||
new BufferedTransformStream((stream) => {
|
new BufferedTransformStream((stream) => {
|
||||||
return deserializeAndroidLogEntry(stream);
|
return deserializeAndroidLogEntry(stream);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import type { Adb } from "@yume-chan/adb";
|
import type { Adb } from "@yume-chan/adb";
|
||||||
import { AdbCommandBase } from "@yume-chan/adb";
|
import { AdbServiceBase } from "@yume-chan/adb";
|
||||||
|
|
||||||
import { Settings } from "./settings.js";
|
import { Settings } from "./settings.js";
|
||||||
import { p } from "./string-format.js";
|
import { p } from "./string-format.js";
|
||||||
|
@ -17,7 +17,7 @@ export interface OverlayDisplayDevice {
|
||||||
showSystemDecorations: boolean;
|
showSystemDecorations: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class OverlayDisplay extends AdbCommandBase {
|
export class OverlayDisplay extends AdbServiceBase {
|
||||||
#settings: Settings;
|
#settings: Settings;
|
||||||
|
|
||||||
static readonly SETTING_KEY = "overlay_display_devices";
|
static readonly SETTING_KEY = "overlay_display_devices";
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
// cspell:ignore versioncode
|
// cspell:ignore versioncode
|
||||||
|
|
||||||
import type { Adb } from "@yume-chan/adb";
|
import type { Adb } from "@yume-chan/adb";
|
||||||
import { AdbCommandBase, escapeArg } from "@yume-chan/adb";
|
import { AdbServiceBase } from "@yume-chan/adb";
|
||||||
import type { MaybeConsumable, ReadableStream } from "@yume-chan/stream-extra";
|
import type { MaybeConsumable, ReadableStream } from "@yume-chan/stream-extra";
|
||||||
import {
|
import {
|
||||||
ConcatStringStream,
|
ConcatStringStream,
|
||||||
|
@ -12,7 +12,7 @@ import {
|
||||||
TextDecoderStream,
|
TextDecoderStream,
|
||||||
} from "@yume-chan/stream-extra";
|
} from "@yume-chan/stream-extra";
|
||||||
|
|
||||||
import { Cmd } from "./cmd.js";
|
import { CmdNoneProtocolService } from "./cmd.js";
|
||||||
import type { IntentBuilder } from "./intent.js";
|
import type { IntentBuilder } from "./intent.js";
|
||||||
import type { SingleUserOrAll } from "./utils.js";
|
import type { SingleUserOrAll } from "./utils.js";
|
||||||
import { buildArguments } from "./utils.js";
|
import { buildArguments } from "./utils.js";
|
||||||
|
@ -260,7 +260,7 @@ function buildInstallArguments(
|
||||||
options: Partial<PackageManagerInstallOptions> | undefined,
|
options: Partial<PackageManagerInstallOptions> | undefined,
|
||||||
): string[] {
|
): string[] {
|
||||||
const args = buildArguments(
|
const args = buildArguments(
|
||||||
["pm", command],
|
[PackageManager.ServiceName, command],
|
||||||
options,
|
options,
|
||||||
PACKAGE_MANAGER_INSTALL_OPTIONS_MAP,
|
PACKAGE_MANAGER_INSTALL_OPTIONS_MAP,
|
||||||
);
|
);
|
||||||
|
@ -281,12 +281,15 @@ function buildInstallArguments(
|
||||||
return args;
|
return args;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PackageManager extends AdbCommandBase {
|
export class PackageManager extends AdbServiceBase {
|
||||||
#cmd: Cmd;
|
static ServiceName = "package";
|
||||||
|
static CommandName = "pm";
|
||||||
|
|
||||||
|
#cmd: CmdNoneProtocolService;
|
||||||
|
|
||||||
constructor(adb: Adb) {
|
constructor(adb: Adb) {
|
||||||
super(adb);
|
super(adb);
|
||||||
this.#cmd = new Cmd(adb);
|
this.#cmd = new CmdNoneProtocolService(adb, PackageManager.CommandName);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -299,28 +302,9 @@ export class PackageManager extends AdbCommandBase {
|
||||||
options?: Partial<PackageManagerInstallOptions>,
|
options?: Partial<PackageManagerInstallOptions>,
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
const args = buildInstallArguments("install", options);
|
const args = buildInstallArguments("install", options);
|
||||||
|
args[0] = PackageManager.CommandName;
|
||||||
// WIP: old version of pm doesn't support multiple apks
|
// WIP: old version of pm doesn't support multiple apks
|
||||||
args.push(...apks);
|
args.push(...apks);
|
||||||
return await this.adb.subprocess.spawnAndWaitLegacy(args);
|
|
||||||
}
|
|
||||||
|
|
||||||
async pushAndInstallStream(
|
|
||||||
stream: ReadableStream<MaybeConsumable<Uint8Array>>,
|
|
||||||
options?: Partial<PackageManagerInstallOptions>,
|
|
||||||
): Promise<void> {
|
|
||||||
const sync = await this.adb.sync();
|
|
||||||
|
|
||||||
const fileName = Math.random().toString().substring(2);
|
|
||||||
const filePath = `/data/local/tmp/${fileName}.apk`;
|
|
||||||
|
|
||||||
try {
|
|
||||||
await sync.write({
|
|
||||||
filename: filePath,
|
|
||||||
file: stream,
|
|
||||||
});
|
|
||||||
} finally {
|
|
||||||
await sync.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Starting from Android 7, `pm` becomes a wrapper to `cmd package`.
|
// Starting from Android 7, `pm` becomes a wrapper to `cmd package`.
|
||||||
// The benefit of `cmd package` is it starts faster than the old `pm`,
|
// The benefit of `cmd package` is it starts faster than the old `pm`,
|
||||||
|
@ -331,17 +315,37 @@ export class PackageManager extends AdbCommandBase {
|
||||||
// read files in `/data/local/tmp` (and many other places) due to SELinux policies,
|
// read files in `/data/local/tmp` (and many other places) due to SELinux policies,
|
||||||
// so installing files must still use `pm`.
|
// so installing files must still use `pm`.
|
||||||
// (the starting executable file decides which SELinux policies to apply)
|
// (the starting executable file decides which SELinux policies to apply)
|
||||||
const args = buildInstallArguments("install", options);
|
const output = await this.adb.subprocess.noneProtocol
|
||||||
args.push(filePath);
|
.spawnWaitText(args)
|
||||||
|
.then((output) => output.trim());
|
||||||
|
|
||||||
|
if (output !== "Success") {
|
||||||
|
throw new Error(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
async pushAndInstallStream(
|
||||||
|
stream: ReadableStream<MaybeConsumable<Uint8Array>>,
|
||||||
|
options?: Partial<PackageManagerInstallOptions>,
|
||||||
|
): Promise<string> {
|
||||||
|
const fileName = Math.random().toString().substring(2);
|
||||||
|
const filePath = `/data/local/tmp/${fileName}.apk`;
|
||||||
|
|
||||||
|
const sync = await this.adb.sync();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const output = await this.adb.subprocess
|
await sync.write({
|
||||||
.spawnAndWaitLegacy(args.map(escapeArg))
|
filename: filePath,
|
||||||
.then((output) => output.trim());
|
file: stream,
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
await sync.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
if (output !== "Success") {
|
try {
|
||||||
throw new Error(output);
|
return await this.install([filePath], options);
|
||||||
}
|
|
||||||
} finally {
|
} finally {
|
||||||
await this.adb.rm(filePath);
|
await this.adb.rm(filePath);
|
||||||
}
|
}
|
||||||
|
@ -356,20 +360,17 @@ export class PackageManager extends AdbCommandBase {
|
||||||
// It's hard to detect whether `pm` supports streaming install (unless actually trying),
|
// It's hard to detect whether `pm` supports streaming install (unless actually trying),
|
||||||
// so check for whether `cmd` is supported,
|
// so check for whether `cmd` is supported,
|
||||||
// and assume `pm` streaming install support status is same as that.
|
// and assume `pm` streaming install support status is same as that.
|
||||||
if (!this.#cmd.supportsCmd) {
|
if (!this.#cmd.isSupported) {
|
||||||
// Fall back to push file then install
|
// Fall back to push file then install
|
||||||
await this.pushAndInstallStream(stream, options);
|
await this.pushAndInstallStream(stream, options);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const args = buildInstallArguments("install", options);
|
const args = buildInstallArguments("install", options);
|
||||||
// Remove `pm` from args, `Cmd#spawn` will prepend `cmd <command>` so the final args
|
|
||||||
// will be `cmd package install <args>`
|
|
||||||
args.shift();
|
|
||||||
args.push("-S", size.toString());
|
args.push("-S", size.toString());
|
||||||
const process = await this.#cmd.spawn(false, "package", ...args);
|
const process = await this.#cmd.spawn(args);
|
||||||
|
|
||||||
const output = process.stdout
|
const output = process.output
|
||||||
.pipeThrough(new TextDecoderStream())
|
.pipeThrough(new TextDecoderStream())
|
||||||
.pipeThrough(new ConcatStringStream())
|
.pipeThrough(new ConcatStringStream())
|
||||||
.then((output) => output.trim());
|
.then((output) => output.trim());
|
||||||
|
@ -437,20 +438,11 @@ export class PackageManager extends AdbCommandBase {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async #cmdOrSubprocess(args: string[]) {
|
|
||||||
if (this.#cmd.supportsCmd) {
|
|
||||||
args.shift();
|
|
||||||
return await this.#cmd.spawn(false, "package", ...args);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.adb.subprocess.spawn(args);
|
|
||||||
}
|
|
||||||
|
|
||||||
async *listPackages(
|
async *listPackages(
|
||||||
options?: Partial<PackageManagerListPackagesOptions>,
|
options?: Partial<PackageManagerListPackagesOptions>,
|
||||||
): AsyncGenerator<PackageManagerListPackagesResult, void, void> {
|
): AsyncGenerator<PackageManagerListPackagesResult, void, void> {
|
||||||
const args = buildArguments(
|
const args = buildArguments(
|
||||||
["pm", "list", "packages"],
|
["package", "list", "packages"],
|
||||||
options,
|
options,
|
||||||
PACKAGE_MANAGER_LIST_PACKAGES_OPTIONS_MAP,
|
PACKAGE_MANAGER_LIST_PACKAGES_OPTIONS_MAP,
|
||||||
);
|
);
|
||||||
|
@ -458,12 +450,9 @@ export class PackageManager extends AdbCommandBase {
|
||||||
args.push(options.filter);
|
args.push(options.filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
const process = await this.#cmdOrSubprocess(args);
|
const process = await this.#cmd.spawn(args);
|
||||||
const reader = process.stdout
|
const reader = process.output
|
||||||
.pipeThrough(new TextDecoderStream())
|
.pipeThrough(new TextDecoderStream())
|
||||||
// FIXME: `SplitStringStream` will throw away some data
|
|
||||||
// if it doesn't end with a separator. So each chunk of data
|
|
||||||
// must contain several complete lines.
|
|
||||||
.pipeThrough(new SplitStringStream("\n"))
|
.pipeThrough(new SplitStringStream("\n"))
|
||||||
.getReader();
|
.getReader();
|
||||||
while (true) {
|
while (true) {
|
||||||
|
@ -475,12 +464,11 @@ export class PackageManager extends AdbCommandBase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getPackages(packageName: string): Promise<string[]> {
|
async getPackageSources(packageName: string): Promise<string[]> {
|
||||||
const args = ["pm", "-p", packageName];
|
const args = [PackageManager.ServiceName, "-p", packageName];
|
||||||
|
const process = await this.#cmd.spawn(args);
|
||||||
const process = await this.#cmdOrSubprocess(args);
|
|
||||||
const result: string[] = [];
|
const result: string[] = [];
|
||||||
for await (const line of process.stdout
|
for await (const line of process.output
|
||||||
.pipeThrough(new TextDecoderStream())
|
.pipeThrough(new TextDecoderStream())
|
||||||
.pipeThrough(new SplitStringStream("\n"))) {
|
.pipeThrough(new SplitStringStream("\n"))) {
|
||||||
if (line.startsWith("package:")) {
|
if (line.startsWith("package:")) {
|
||||||
|
@ -496,7 +484,7 @@ export class PackageManager extends AdbCommandBase {
|
||||||
options?: Partial<PackageManagerUninstallOptions>,
|
options?: Partial<PackageManagerUninstallOptions>,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const args = buildArguments(
|
const args = buildArguments(
|
||||||
["pm", "uninstall"],
|
[PackageManager.ServiceName, "uninstall"],
|
||||||
options,
|
options,
|
||||||
PACKAGE_MANAGER_UNINSTALL_OPTIONS_MAP,
|
PACKAGE_MANAGER_UNINSTALL_OPTIONS_MAP,
|
||||||
);
|
);
|
||||||
|
@ -505,10 +493,8 @@ export class PackageManager extends AdbCommandBase {
|
||||||
args.push(...options.splitNames);
|
args.push(...options.splitNames);
|
||||||
}
|
}
|
||||||
|
|
||||||
const process = await this.#cmdOrSubprocess(args);
|
const output = await this.#cmd
|
||||||
const output = await process.stdout
|
.spawnWaitText(args)
|
||||||
.pipeThrough(new TextDecoderStream())
|
|
||||||
.pipeThrough(new ConcatStringStream())
|
|
||||||
.then((output) => output.trim());
|
.then((output) => output.trim());
|
||||||
if (output !== "Success") {
|
if (output !== "Success") {
|
||||||
throw new Error(output);
|
throw new Error(output);
|
||||||
|
@ -519,17 +505,15 @@ export class PackageManager extends AdbCommandBase {
|
||||||
options: PackageManagerResolveActivityOptions,
|
options: PackageManagerResolveActivityOptions,
|
||||||
): Promise<string | undefined> {
|
): Promise<string | undefined> {
|
||||||
let args = buildArguments(
|
let args = buildArguments(
|
||||||
["pm", "resolve-activity", "--components"],
|
[PackageManager.ServiceName, "resolve-activity", "--components"],
|
||||||
options,
|
options,
|
||||||
PACKAGE_MANAGER_RESOLVE_ACTIVITY_OPTIONS_MAP,
|
PACKAGE_MANAGER_RESOLVE_ACTIVITY_OPTIONS_MAP,
|
||||||
);
|
);
|
||||||
|
|
||||||
args = args.concat(options.intent.build());
|
args = args.concat(options.intent.build());
|
||||||
|
|
||||||
const process = await this.#cmdOrSubprocess(args);
|
const output = await this.#cmd
|
||||||
const output = await process.stdout
|
.spawnWaitText(args)
|
||||||
.pipeThrough(new TextDecoderStream())
|
|
||||||
.pipeThrough(new ConcatStringStream())
|
|
||||||
.then((output) => output.trim());
|
.then((output) => output.trim());
|
||||||
|
|
||||||
if (output === "No activity found") {
|
if (output === "No activity found") {
|
||||||
|
@ -554,10 +538,8 @@ export class PackageManager extends AdbCommandBase {
|
||||||
): Promise<number> {
|
): Promise<number> {
|
||||||
const args = buildInstallArguments("install-create", options);
|
const args = buildInstallArguments("install-create", options);
|
||||||
|
|
||||||
const process = await this.#cmdOrSubprocess(args);
|
const output = await this.#cmd
|
||||||
const output = await process.stdout
|
.spawnWaitText(args)
|
||||||
.pipeThrough(new TextDecoderStream())
|
|
||||||
.pipeThrough(new ConcatStringStream())
|
|
||||||
.then((output) => output.trim());
|
.then((output) => output.trim());
|
||||||
|
|
||||||
const sessionIdString = output.match(/.*\[(\d+)\].*/);
|
const sessionIdString = output.match(/.*\[(\d+)\].*/);
|
||||||
|
@ -568,6 +550,17 @@ export class PackageManager extends AdbCommandBase {
|
||||||
return Number.parseInt(sessionIdString[1]!, 10);
|
return Number.parseInt(sessionIdString[1]!, 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async checkResult(stream: ReadableStream<Uint8Array>) {
|
||||||
|
const output = await stream
|
||||||
|
.pipeThrough(new TextDecoderStream())
|
||||||
|
.pipeThrough(new ConcatStringStream())
|
||||||
|
.then((output) => output.trim());
|
||||||
|
|
||||||
|
if (!output.startsWith("Success")) {
|
||||||
|
throw new Error(output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async sessionAddSplit(
|
async sessionAddSplit(
|
||||||
sessionId: number,
|
sessionId: number,
|
||||||
splitName: string,
|
splitName: string,
|
||||||
|
@ -581,12 +574,8 @@ export class PackageManager extends AdbCommandBase {
|
||||||
path,
|
path,
|
||||||
];
|
];
|
||||||
|
|
||||||
const output = await this.adb.subprocess
|
const process = await this.adb.subprocess.noneProtocol.spawn(args);
|
||||||
.spawnAndWaitLegacy(args)
|
await this.checkResult(process.output);
|
||||||
.then((output) => output.trim());
|
|
||||||
if (!output.startsWith("Success")) {
|
|
||||||
throw new Error(output);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async sessionAddSplitStream(
|
async sessionAddSplitStream(
|
||||||
|
@ -596,7 +585,7 @@ export class PackageManager extends AdbCommandBase {
|
||||||
stream: ReadableStream<MaybeConsumable<Uint8Array>>,
|
stream: ReadableStream<MaybeConsumable<Uint8Array>>,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const args: string[] = [
|
const args: string[] = [
|
||||||
"pm",
|
PackageManager.ServiceName,
|
||||||
"install-write",
|
"install-write",
|
||||||
"-S",
|
"-S",
|
||||||
size.toString(),
|
size.toString(),
|
||||||
|
@ -605,40 +594,31 @@ export class PackageManager extends AdbCommandBase {
|
||||||
"-",
|
"-",
|
||||||
];
|
];
|
||||||
|
|
||||||
const process = await this.#cmdOrSubprocess(args);
|
const process = await this.#cmd.spawn(args);
|
||||||
const output = process.stdout
|
|
||||||
.pipeThrough(new TextDecoderStream())
|
|
||||||
.pipeThrough(new ConcatStringStream())
|
|
||||||
.then((output) => output.trim());
|
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
stream.pipeTo(process.stdin),
|
stream.pipeTo(process.stdin),
|
||||||
output.then((output) => {
|
this.checkResult(process.output),
|
||||||
if (!output.startsWith("Success")) {
|
|
||||||
throw new Error(output);
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
async sessionCommit(sessionId: number): Promise<void> {
|
async sessionCommit(sessionId: number): Promise<void> {
|
||||||
const args: string[] = ["pm", "install-commit", sessionId.toString()];
|
const args: string[] = [
|
||||||
const output = await this.adb.subprocess
|
PackageManager.ServiceName,
|
||||||
.spawnAndWaitLegacy(args)
|
"install-commit",
|
||||||
.then((output) => output.trim());
|
sessionId.toString(),
|
||||||
if (output !== "Success") {
|
];
|
||||||
throw new Error(output);
|
const process = await this.#cmd.spawn(args);
|
||||||
}
|
await this.checkResult(process.output);
|
||||||
}
|
}
|
||||||
|
|
||||||
async sessionAbandon(sessionId: number): Promise<void> {
|
async sessionAbandon(sessionId: number): Promise<void> {
|
||||||
const args: string[] = ["pm", "install-abandon", sessionId.toString()];
|
const args: string[] = [
|
||||||
const output = await this.adb.subprocess
|
PackageManager.ServiceName,
|
||||||
.spawnAndWaitLegacy(args)
|
"install-abandon",
|
||||||
.then((output) => output.trim());
|
sessionId.toString(),
|
||||||
if (output !== "Success") {
|
];
|
||||||
throw new Error(output);
|
const process = await this.#cmd.spawn(args);
|
||||||
}
|
await this.checkResult(process.output);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import type { Adb, AdbSubprocessWaitResult } from "@yume-chan/adb";
|
import type { Adb } from "@yume-chan/adb";
|
||||||
import { AdbCommandBase } from "@yume-chan/adb";
|
import { AdbServiceBase } from "@yume-chan/adb";
|
||||||
|
|
||||||
import { Cmd } from "./cmd.js";
|
import { CmdNoneProtocolService } from "./cmd.js";
|
||||||
import type { SingleUser } from "./utils.js";
|
import type { SingleUser } from "./utils.js";
|
||||||
|
|
||||||
export type SettingsNamespace = "system" | "secure" | "global";
|
export type SettingsNamespace = "system" | "secure" | "global";
|
||||||
|
@ -25,21 +25,24 @@ export interface SettingsPutOptions extends SettingsOptions {
|
||||||
}
|
}
|
||||||
|
|
||||||
// frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/SettingsService.java
|
// frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/SettingsService.java
|
||||||
export class Settings extends AdbCommandBase {
|
export class Settings extends AdbServiceBase {
|
||||||
#cmd: Cmd;
|
static ServiceName = "settings";
|
||||||
|
static CommandName = "settings";
|
||||||
|
|
||||||
|
#cmd: CmdNoneProtocolService;
|
||||||
|
|
||||||
constructor(adb: Adb) {
|
constructor(adb: Adb) {
|
||||||
super(adb);
|
super(adb);
|
||||||
this.#cmd = new Cmd(adb);
|
this.#cmd = new CmdNoneProtocolService(adb, Settings.CommandName);
|
||||||
}
|
}
|
||||||
|
|
||||||
async base(
|
base(
|
||||||
verb: string,
|
verb: string,
|
||||||
namespace: SettingsNamespace,
|
namespace: SettingsNamespace,
|
||||||
options: SettingsOptions | undefined,
|
options: SettingsOptions | undefined,
|
||||||
...args: string[]
|
...args: string[]
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
let command = ["settings"];
|
let command = [Settings.ServiceName];
|
||||||
|
|
||||||
if (options?.user !== undefined) {
|
if (options?.user !== undefined) {
|
||||||
command.push("--user", options.user.toString());
|
command.push("--user", options.user.toString());
|
||||||
|
@ -48,21 +51,7 @@ export class Settings extends AdbCommandBase {
|
||||||
command.push(verb, namespace);
|
command.push(verb, namespace);
|
||||||
command = command.concat(args);
|
command = command.concat(args);
|
||||||
|
|
||||||
let output: AdbSubprocessWaitResult;
|
return this.#cmd.spawnWaitText(command);
|
||||||
if (this.#cmd.supportsCmd) {
|
|
||||||
output = await this.#cmd.spawnAndWait(
|
|
||||||
command[0]!,
|
|
||||||
...command.slice(1),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
output = await this.adb.subprocess.spawnAndWait(command);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (output.stderr) {
|
|
||||||
throw new Error(output.stderr);
|
|
||||||
}
|
|
||||||
|
|
||||||
return output.stdout;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async get(
|
async get(
|
||||||
|
|
|
@ -40,8 +40,8 @@ export class ScrcpyOptionsWrapper<T extends object>
|
||||||
return this.#base.uHidOutput;
|
return this.#base.uHidOutput;
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(options: ScrcpyOptions<T>) {
|
constructor(base: ScrcpyOptions<T>) {
|
||||||
this.#base = options;
|
this.#base = base;
|
||||||
}
|
}
|
||||||
|
|
||||||
serialize(): string[] {
|
serialize(): string[] {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue