mirror of
https://github.com/yume-chan/ya-webadb.git
synced 2025-10-06 03:50:18 +02:00
refactor: migrate to ES private fields
This commit is contained in:
parent
433f9b986f
commit
7056feb3b1
108 changed files with 1264 additions and 1294 deletions
|
@ -71,7 +71,7 @@ export default class AdbWebCredentialStore implements AdbCredentialStore {
|
|||
*
|
||||
* @returns The private key in PKCS #8 format.
|
||||
*/
|
||||
public async generateKey(): Promise<Uint8Array> {
|
||||
async generateKey(): Promise<Uint8Array> {
|
||||
const { privateKey: cryptoKey } = await crypto.subtle.generateKey(
|
||||
{
|
||||
name: "RSASSA-PKCS1-v1_5",
|
||||
|
@ -97,7 +97,7 @@ export default class AdbWebCredentialStore implements AdbCredentialStore {
|
|||
*
|
||||
* This method returns a generator, so `for await...of...` loop should be used to read the key.
|
||||
*/
|
||||
public async *iterateKeys(): AsyncGenerator<Uint8Array, void, void> {
|
||||
async *iterateKeys(): AsyncGenerator<Uint8Array, void, void> {
|
||||
for (const key of await getAllKeys()) {
|
||||
yield key;
|
||||
}
|
||||
|
|
|
@ -46,26 +46,26 @@ declare global {
|
|||
}
|
||||
|
||||
export default class AdbDaemonDirectSocketsDevice implements AdbDaemonDevice {
|
||||
public static isSupported(): boolean {
|
||||
static isSupported(): boolean {
|
||||
return typeof globalThis.TCPSocket !== "undefined";
|
||||
}
|
||||
|
||||
public readonly serial: string;
|
||||
readonly serial: string;
|
||||
|
||||
public readonly host: string;
|
||||
readonly host: string;
|
||||
|
||||
public readonly port: number;
|
||||
readonly port: number;
|
||||
|
||||
public name: string | undefined;
|
||||
name: string | undefined;
|
||||
|
||||
public constructor(host: string, port = 5555, name?: string) {
|
||||
constructor(host: string, port = 5555, name?: string) {
|
||||
this.host = host;
|
||||
this.port = port;
|
||||
this.serial = `${host}:${port}`;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public async connect() {
|
||||
async connect() {
|
||||
const socket = new globalThis.TCPSocket(this.host, this.port, {
|
||||
noDelay: true,
|
||||
});
|
||||
|
|
|
@ -77,16 +77,16 @@ class Uint8ArrayExactReadable implements ExactReadable {
|
|||
#data: Uint8Array;
|
||||
#position: number;
|
||||
|
||||
public get position() {
|
||||
get position() {
|
||||
return this.#position;
|
||||
}
|
||||
|
||||
public constructor(data: Uint8Array) {
|
||||
constructor(data: Uint8Array) {
|
||||
this.#data = data;
|
||||
this.#position = 0;
|
||||
}
|
||||
|
||||
public readExactly(length: number): Uint8Array {
|
||||
readExactly(length: number): Uint8Array {
|
||||
const result = this.#data.subarray(
|
||||
this.#position,
|
||||
this.#position + length,
|
||||
|
@ -100,16 +100,16 @@ export class AdbDaemonWebUsbConnection
|
|||
implements ReadableWritablePair<AdbPacketData, Consumable<AdbPacketInit>>
|
||||
{
|
||||
#readable: ReadableStream<AdbPacketData>;
|
||||
public get readable() {
|
||||
get readable() {
|
||||
return this.#readable;
|
||||
}
|
||||
|
||||
#writable: WritableStream<Consumable<AdbPacketInit>>;
|
||||
public get writable() {
|
||||
get writable() {
|
||||
return this.#writable;
|
||||
}
|
||||
|
||||
public constructor(
|
||||
constructor(
|
||||
device: USBDevice,
|
||||
inEndpoint: USBEndpoint,
|
||||
outEndpoint: USBEndpoint,
|
||||
|
@ -249,15 +249,15 @@ export class AdbDaemonWebUsbDevice implements AdbDaemonDevice {
|
|||
#usbManager: USB;
|
||||
|
||||
#raw: USBDevice;
|
||||
public get raw() {
|
||||
get raw() {
|
||||
return this.#raw;
|
||||
}
|
||||
|
||||
public get serial(): string {
|
||||
get serial(): string {
|
||||
return this.#raw.serialNumber!;
|
||||
}
|
||||
|
||||
public get name(): string {
|
||||
get name(): string {
|
||||
return this.#raw.productName!;
|
||||
}
|
||||
|
||||
|
@ -267,7 +267,7 @@ export class AdbDaemonWebUsbDevice implements AdbDaemonDevice {
|
|||
* @param device The `USBDevice` instance obtained elsewhere.
|
||||
* @param filters The filters to use when searching for ADB interface. Defaults to {@link ADB_DEFAULT_DEVICE_FILTER}.
|
||||
*/
|
||||
public constructor(
|
||||
constructor(
|
||||
device: USBDevice,
|
||||
filters: AdbDeviceFilter[] = [ADB_DEFAULT_DEVICE_FILTER],
|
||||
usbManager: USB,
|
||||
|
@ -281,7 +281,7 @@ export class AdbDaemonWebUsbDevice implements AdbDaemonDevice {
|
|||
* Claim the device and create a pair of `AdbPacket` streams to the ADB interface.
|
||||
* @returns The pair of `AdbPacket` streams.
|
||||
*/
|
||||
public async connect(): Promise<
|
||||
async connect(): Promise<
|
||||
ReadableWritablePair<AdbPacketData, Consumable<AdbPacketInit>>
|
||||
> {
|
||||
if (!this.#raw.opened) {
|
||||
|
|
|
@ -8,7 +8,7 @@ export class AdbDaemonWebUsbDeviceManager {
|
|||
*
|
||||
* May be `undefined` if current runtime does not support WebUSB.
|
||||
*/
|
||||
public static readonly BROWSER =
|
||||
static readonly BROWSER =
|
||||
typeof globalThis.navigator !== "undefined" &&
|
||||
!!globalThis.navigator.usb
|
||||
? new AdbDaemonWebUsbDeviceManager(globalThis.navigator.usb)
|
||||
|
@ -20,7 +20,7 @@ export class AdbDaemonWebUsbDeviceManager {
|
|||
* Create a new instance of {@link AdbDaemonWebUsbDeviceManager} using the specified WebUSB implementation.
|
||||
* @param usbManager A WebUSB compatible interface.
|
||||
*/
|
||||
public constructor(usbManager: USB) {
|
||||
constructor(usbManager: USB) {
|
||||
this.#usbManager = usbManager;
|
||||
}
|
||||
|
||||
|
@ -37,7 +37,7 @@ export class AdbDaemonWebUsbDeviceManager {
|
|||
* @returns An {@link AdbDaemonWebUsbDevice} instance if the user selected a device,
|
||||
* or `undefined` if the user cancelled the device picker.
|
||||
*/
|
||||
public async requestDevice(
|
||||
async requestDevice(
|
||||
filters: AdbDeviceFilter[] = [ADB_DEFAULT_DEVICE_FILTER],
|
||||
): Promise<AdbDaemonWebUsbDevice | undefined> {
|
||||
try {
|
||||
|
@ -67,7 +67,7 @@ export class AdbDaemonWebUsbDeviceManager {
|
|||
* Defaults to {@link ADB_DEFAULT_DEVICE_FILTER}.
|
||||
* @returns An array of {@link AdbDaemonWebUsbDevice} instances for all connected and authenticated devices.
|
||||
*/
|
||||
public async getDevices(
|
||||
async getDevices(
|
||||
filters: AdbDeviceFilter[] = [ADB_DEFAULT_DEVICE_FILTER],
|
||||
): Promise<AdbDaemonWebUsbDevice[]> {
|
||||
const devices = await this.#usbManager.getDevices();
|
||||
|
|
|
@ -2,27 +2,27 @@ export class AdbDaemonWebUsbDeviceWatcher {
|
|||
#callback: (newDeviceSerial?: string) => void;
|
||||
#usbManager: USB;
|
||||
|
||||
public constructor(callback: (newDeviceSerial?: string) => void, usb: USB) {
|
||||
constructor(callback: (newDeviceSerial?: string) => void, usb: USB) {
|
||||
this.#callback = callback;
|
||||
this.#usbManager = usb;
|
||||
|
||||
this.#usbManager.addEventListener("connect", this.handleConnect);
|
||||
this.#usbManager.addEventListener("disconnect", this.handleDisconnect);
|
||||
this.#usbManager.addEventListener("connect", this.#handleConnect);
|
||||
this.#usbManager.addEventListener("disconnect", this.#handleDisconnect);
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this.#usbManager.removeEventListener("connect", this.handleConnect);
|
||||
dispose(): void {
|
||||
this.#usbManager.removeEventListener("connect", this.#handleConnect);
|
||||
this.#usbManager.removeEventListener(
|
||||
"disconnect",
|
||||
this.handleDisconnect,
|
||||
this.#handleDisconnect,
|
||||
);
|
||||
}
|
||||
|
||||
private handleConnect = (e: USBConnectionEvent) => {
|
||||
#handleConnect = (e: USBConnectionEvent) => {
|
||||
this.#callback(e.device.serialNumber);
|
||||
};
|
||||
|
||||
private handleDisconnect = () => {
|
||||
#handleDisconnect = () => {
|
||||
this.#callback();
|
||||
};
|
||||
}
|
||||
|
|
|
@ -14,16 +14,16 @@ import {
|
|||
} from "@yume-chan/stream-extra";
|
||||
|
||||
export default class AdbDaemonWebSocketDevice implements AdbDaemonDevice {
|
||||
public readonly serial: string;
|
||||
readonly serial: string;
|
||||
|
||||
public name: string | undefined;
|
||||
name: string | undefined;
|
||||
|
||||
public constructor(url: string, name?: string) {
|
||||
constructor(url: string, name?: string) {
|
||||
this.serial = url;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public async connect() {
|
||||
async connect() {
|
||||
const socket = new WebSocket(this.serial);
|
||||
socket.binaryType = "arraybuffer";
|
||||
|
||||
|
|
|
@ -62,9 +62,9 @@ function concatStreams<T>(...streams: ReadableStream<T>[]): ReadableStream<T> {
|
|||
}
|
||||
|
||||
export class AdbScrcpyExitedError extends Error {
|
||||
public output: string[];
|
||||
output: string[];
|
||||
|
||||
public constructor(output: string[]) {
|
||||
constructor(output: string[]) {
|
||||
super("scrcpy server exited prematurely");
|
||||
this.output = output;
|
||||
}
|
||||
|
@ -98,7 +98,7 @@ export type AdbScrcpyAudioStreamMetadata =
|
|||
| AdbScrcpyAudioStreamSuccessMetadata;
|
||||
|
||||
export class AdbScrcpyClient {
|
||||
public static async pushServer(
|
||||
static async pushServer(
|
||||
adb: Adb,
|
||||
file: ReadableStream<Consumable<Uint8Array>>,
|
||||
filename = DEFAULT_SERVER_PATH,
|
||||
|
@ -114,7 +114,7 @@ export class AdbScrcpyClient {
|
|||
}
|
||||
}
|
||||
|
||||
public static async start(
|
||||
static async start(
|
||||
adb: Adb,
|
||||
path: string,
|
||||
version: string,
|
||||
|
@ -213,7 +213,7 @@ export class AdbScrcpyClient {
|
|||
* This method will modify the given `options`,
|
||||
* so don't reuse it elsewhere.
|
||||
*/
|
||||
public static async getEncoders(
|
||||
static async getEncoders(
|
||||
adb: Adb,
|
||||
path: string,
|
||||
version: string,
|
||||
|
@ -227,7 +227,7 @@ export class AdbScrcpyClient {
|
|||
* This method will modify the given `options`,
|
||||
* so don't reuse it elsewhere.
|
||||
*/
|
||||
public static async getDisplays(
|
||||
static async getDisplays(
|
||||
adb: Adb,
|
||||
path: string,
|
||||
version: string,
|
||||
|
@ -237,51 +237,49 @@ export class AdbScrcpyClient {
|
|||
return await options.getDisplays(adb, path, version);
|
||||
}
|
||||
|
||||
private _options: AdbScrcpyOptions<object>;
|
||||
private _process: AdbSubprocessProtocol;
|
||||
#options: AdbScrcpyOptions<object>;
|
||||
#process: AdbSubprocessProtocol;
|
||||
|
||||
private _stdout: ReadableStream<string>;
|
||||
public get stdout() {
|
||||
return this._stdout;
|
||||
#stdout: ReadableStream<string>;
|
||||
get stdout() {
|
||||
return this.#stdout;
|
||||
}
|
||||
|
||||
public get exit() {
|
||||
return this._process.exit;
|
||||
get exit() {
|
||||
return this.#process.exit;
|
||||
}
|
||||
|
||||
private _screenWidth: number | undefined;
|
||||
public get screenWidth() {
|
||||
return this._screenWidth;
|
||||
#screenWidth: number | undefined;
|
||||
get screenWidth() {
|
||||
return this.#screenWidth;
|
||||
}
|
||||
|
||||
private _screenHeight: number | undefined;
|
||||
public get screenHeight() {
|
||||
return this._screenHeight;
|
||||
#screenHeight: number | undefined;
|
||||
get screenHeight() {
|
||||
return this.#screenHeight;
|
||||
}
|
||||
|
||||
private _videoStream: Promise<AdbScrcpyVideoStream> | undefined;
|
||||
public get videoStream() {
|
||||
return this._videoStream;
|
||||
#videoStream: Promise<AdbScrcpyVideoStream> | undefined;
|
||||
get videoStream() {
|
||||
return this.#videoStream;
|
||||
}
|
||||
|
||||
private _audioStream: Promise<AdbScrcpyAudioStreamMetadata> | undefined;
|
||||
public get audioStream() {
|
||||
return this._audioStream;
|
||||
#audioStream: Promise<AdbScrcpyAudioStreamMetadata> | undefined;
|
||||
get audioStream() {
|
||||
return this.#audioStream;
|
||||
}
|
||||
|
||||
private _controlMessageWriter: ScrcpyControlMessageWriter | undefined;
|
||||
public get controlMessageWriter() {
|
||||
return this._controlMessageWriter;
|
||||
#controlMessageWriter: ScrcpyControlMessageWriter | undefined;
|
||||
get controlMessageWriter() {
|
||||
return this.#controlMessageWriter;
|
||||
}
|
||||
|
||||
private _deviceMessageStream:
|
||||
| ReadableStream<ScrcpyDeviceMessage>
|
||||
| undefined;
|
||||
public get deviceMessageStream() {
|
||||
return this._deviceMessageStream;
|
||||
#deviceMessageStream: ReadableStream<ScrcpyDeviceMessage> | undefined;
|
||||
get deviceMessageStream() {
|
||||
return this.#deviceMessageStream;
|
||||
}
|
||||
|
||||
public constructor({
|
||||
constructor({
|
||||
options,
|
||||
process,
|
||||
stdout,
|
||||
|
@ -289,36 +287,36 @@ export class AdbScrcpyClient {
|
|||
audioStream,
|
||||
controlStream,
|
||||
}: AdbScrcpyClientInit) {
|
||||
this._options = options;
|
||||
this._process = process;
|
||||
this._stdout = stdout;
|
||||
this.#options = options;
|
||||
this.#process = process;
|
||||
this.#stdout = stdout;
|
||||
|
||||
this._videoStream = videoStream
|
||||
? this.createVideoStream(videoStream)
|
||||
this.#videoStream = videoStream
|
||||
? this.#createVideoStream(videoStream)
|
||||
: undefined;
|
||||
|
||||
this._audioStream = audioStream
|
||||
? this.createAudioStream(audioStream)
|
||||
this.#audioStream = audioStream
|
||||
? this.#createAudioStream(audioStream)
|
||||
: undefined;
|
||||
|
||||
if (controlStream) {
|
||||
this._controlMessageWriter = new ScrcpyControlMessageWriter(
|
||||
this.#controlMessageWriter = new ScrcpyControlMessageWriter(
|
||||
controlStream.writable.getWriter(),
|
||||
options,
|
||||
);
|
||||
this._deviceMessageStream = controlStream.readable.pipeThrough(
|
||||
this.#deviceMessageStream = controlStream.readable.pipeThrough(
|
||||
new ScrcpyDeviceMessageDeserializeStream(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private async createVideoStream(initialStream: ReadableStream<Uint8Array>) {
|
||||
async #createVideoStream(initialStream: ReadableStream<Uint8Array>) {
|
||||
const { stream, metadata } =
|
||||
await this._options.parseVideoStreamMetadata(initialStream);
|
||||
await this.#options.parseVideoStreamMetadata(initialStream);
|
||||
|
||||
return {
|
||||
stream: stream
|
||||
.pipeThrough(this._options.createMediaStreamTransformer())
|
||||
.pipeThrough(this.#options.createMediaStreamTransformer())
|
||||
.pipeThrough(
|
||||
new InspectStream((packet) => {
|
||||
if (packet.type === "configuration") {
|
||||
|
@ -327,16 +325,16 @@ export class AdbScrcpyClient {
|
|||
const { croppedWidth, croppedHeight } =
|
||||
h264ParseConfiguration(packet.data);
|
||||
|
||||
this._screenWidth = croppedWidth;
|
||||
this._screenHeight = croppedHeight;
|
||||
this.#screenWidth = croppedWidth;
|
||||
this.#screenHeight = croppedHeight;
|
||||
break;
|
||||
}
|
||||
case ScrcpyVideoCodecId.H265: {
|
||||
const { croppedWidth, croppedHeight } =
|
||||
h265ParseConfiguration(packet.data);
|
||||
|
||||
this._screenWidth = croppedWidth;
|
||||
this._screenHeight = croppedHeight;
|
||||
this.#screenWidth = croppedWidth;
|
||||
this.#screenHeight = croppedHeight;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -347,10 +345,10 @@ export class AdbScrcpyClient {
|
|||
};
|
||||
}
|
||||
|
||||
private async createAudioStream(
|
||||
async #createAudioStream(
|
||||
initialStream: ReadableStream<Uint8Array>,
|
||||
): Promise<AdbScrcpyAudioStreamMetadata> {
|
||||
const metadata = await this._options.parseAudioStreamMetadata(
|
||||
const metadata = await this.#options.parseAudioStreamMetadata(
|
||||
initialStream,
|
||||
);
|
||||
|
||||
|
@ -362,7 +360,7 @@ export class AdbScrcpyClient {
|
|||
return {
|
||||
...metadata,
|
||||
stream: metadata.stream.pipeThrough(
|
||||
this._options.createMediaStreamTransformer(),
|
||||
this.#options.createMediaStreamTransformer(),
|
||||
),
|
||||
};
|
||||
default:
|
||||
|
@ -374,7 +372,7 @@ export class AdbScrcpyClient {
|
|||
}
|
||||
}
|
||||
|
||||
public async close() {
|
||||
await this._process.kill();
|
||||
async close() {
|
||||
await this.#process.kill();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -47,13 +47,13 @@ export abstract class AdbScrcpyConnection implements Disposable {
|
|||
|
||||
protected socketName: string;
|
||||
|
||||
public constructor(adb: Adb, options: AdbScrcpyConnectionOptions) {
|
||||
constructor(adb: Adb, options: AdbScrcpyConnectionOptions) {
|
||||
this.adb = adb;
|
||||
this.options = options;
|
||||
this.socketName = this.getSocketName();
|
||||
}
|
||||
|
||||
public initialize(): ValueOrPromise<void> {
|
||||
initialize(): ValueOrPromise<void> {
|
||||
// pure virtual method
|
||||
}
|
||||
|
||||
|
@ -65,28 +65,28 @@ export abstract class AdbScrcpyConnection implements Disposable {
|
|||
return socketName;
|
||||
}
|
||||
|
||||
public abstract getStreams(): ValueOrPromise<AdbScrcpyConnectionStreams>;
|
||||
abstract getStreams(): ValueOrPromise<AdbScrcpyConnectionStreams>;
|
||||
|
||||
public dispose(): void {
|
||||
dispose(): void {
|
||||
// pure virtual method
|
||||
}
|
||||
}
|
||||
|
||||
export class AdbScrcpyForwardConnection extends AdbScrcpyConnection {
|
||||
private _disposed = false;
|
||||
#disposed = false;
|
||||
|
||||
private connect(): Promise<
|
||||
#connect(): Promise<
|
||||
ReadableWritablePair<Uint8Array, Consumable<Uint8Array>>
|
||||
> {
|
||||
return this.adb.createSocket(this.socketName);
|
||||
}
|
||||
|
||||
private async connectAndRetry(
|
||||
async #connectAndRetry(
|
||||
sendDummyByte: boolean,
|
||||
): Promise<ReadableWritablePair<Uint8Array, Consumable<Uint8Array>>> {
|
||||
for (let i = 0; !this._disposed && i < 100; i += 1) {
|
||||
for (let i = 0; !this.#disposed && i < 100; i += 1) {
|
||||
try {
|
||||
const stream = await this.connect();
|
||||
const stream = await this.#connect();
|
||||
if (sendDummyByte) {
|
||||
// Can't guarantee the stream will preserve message boundaries,
|
||||
// so buffer the stream
|
||||
|
@ -108,25 +108,25 @@ export class AdbScrcpyForwardConnection extends AdbScrcpyConnection {
|
|||
throw new Error(`Can't connect to server after 100 retries`);
|
||||
}
|
||||
|
||||
public override async getStreams(): Promise<AdbScrcpyConnectionStreams> {
|
||||
override async getStreams(): Promise<AdbScrcpyConnectionStreams> {
|
||||
let { sendDummyByte } = this.options;
|
||||
|
||||
const streams: AdbScrcpyConnectionStreams = {};
|
||||
|
||||
if (this.options.video) {
|
||||
const video = await this.connectAndRetry(sendDummyByte);
|
||||
const video = await this.#connectAndRetry(sendDummyByte);
|
||||
streams.video = video.readable;
|
||||
sendDummyByte = false;
|
||||
}
|
||||
|
||||
if (this.options.audio) {
|
||||
const audio = await this.connectAndRetry(sendDummyByte);
|
||||
const audio = await this.#connectAndRetry(sendDummyByte);
|
||||
streams.audio = audio.readable;
|
||||
sendDummyByte = false;
|
||||
}
|
||||
|
||||
if (this.options.control) {
|
||||
const control = await this.connectAndRetry(sendDummyByte);
|
||||
const control = await this.#connectAndRetry(sendDummyByte);
|
||||
sendDummyByte = false;
|
||||
streams.control = control;
|
||||
}
|
||||
|
@ -134,19 +134,19 @@ export class AdbScrcpyForwardConnection extends AdbScrcpyConnection {
|
|||
return streams;
|
||||
}
|
||||
|
||||
public override dispose(): void {
|
||||
this._disposed = true;
|
||||
override dispose(): void {
|
||||
this.#disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
export class AdbScrcpyReverseConnection extends AdbScrcpyConnection {
|
||||
private streams!: ReadableStreamDefaultReader<
|
||||
#streams!: ReadableStreamDefaultReader<
|
||||
ReadableWritablePair<Uint8Array, Consumable<Uint8Array>>
|
||||
>;
|
||||
|
||||
private address!: string;
|
||||
#address!: string;
|
||||
|
||||
public override async initialize(): Promise<void> {
|
||||
override async initialize(): Promise<void> {
|
||||
// try to unbind first
|
||||
await this.adb.reverse.remove(this.socketName).catch((e) => {
|
||||
if (e instanceof AdbReverseNotSupportedError) {
|
||||
|
@ -160,45 +160,48 @@ export class AdbScrcpyReverseConnection extends AdbScrcpyConnection {
|
|||
ReadableWritablePair<Uint8Array, Consumable<Uint8Array>>,
|
||||
ReadableWritablePair<Uint8Array, Consumable<Uint8Array>>
|
||||
>();
|
||||
this.streams = queue.readable.getReader();
|
||||
this.#streams = queue.readable.getReader();
|
||||
const writer = queue.writable.getWriter();
|
||||
this.address = await this.adb.reverse.add(this.socketName, (socket) => {
|
||||
this.#address = await this.adb.reverse.add(
|
||||
this.socketName,
|
||||
(socket) => {
|
||||
void writer.write(socket);
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
private async accept(): Promise<
|
||||
async #accept(): Promise<
|
||||
ReadableWritablePair<Uint8Array, Consumable<Uint8Array>>
|
||||
> {
|
||||
return (await this.streams.read()).value!;
|
||||
return (await this.#streams.read()).value!;
|
||||
}
|
||||
|
||||
public async getStreams(): Promise<AdbScrcpyConnectionStreams> {
|
||||
async getStreams(): Promise<AdbScrcpyConnectionStreams> {
|
||||
const streams: AdbScrcpyConnectionStreams = {};
|
||||
|
||||
if (this.options.video) {
|
||||
const video = await this.accept();
|
||||
const video = await this.#accept();
|
||||
streams.video = video.readable;
|
||||
}
|
||||
|
||||
if (this.options.audio) {
|
||||
const audio = await this.accept();
|
||||
const audio = await this.#accept();
|
||||
streams.audio = audio.readable;
|
||||
}
|
||||
|
||||
if (this.options.control) {
|
||||
const control = await this.accept();
|
||||
const control = await this.#accept();
|
||||
streams.control = control;
|
||||
}
|
||||
|
||||
return streams;
|
||||
}
|
||||
|
||||
public override dispose() {
|
||||
override dispose() {
|
||||
// Don't await this!
|
||||
// `reverse.remove`'s response will never arrive
|
||||
// before we read all pending data from Scrcpy streams
|
||||
// NOOP: failed to remove reverse tunnel is not a big deal
|
||||
this.adb.reverse.remove(this.address).catch(NOOP);
|
||||
this.adb.reverse.remove(this.#address).catch(NOOP);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ import type { AdbScrcpyOptions } from "./types.js";
|
|||
import { AdbScrcpyOptionsBase } from "./types.js";
|
||||
|
||||
export class AdbScrcpyOptions1_16 extends AdbScrcpyOptionsBase<ScrcpyOptionsInit1_16> {
|
||||
public static createConnection(
|
||||
static createConnection(
|
||||
adb: Adb,
|
||||
connectionOptions: AdbScrcpyConnectionOptions,
|
||||
tunnelForward: boolean,
|
||||
|
@ -32,7 +32,7 @@ export class AdbScrcpyOptions1_16 extends AdbScrcpyOptionsBase<ScrcpyOptionsInit
|
|||
}
|
||||
}
|
||||
|
||||
public static async getEncoders(
|
||||
static async getEncoders(
|
||||
adb: Adb,
|
||||
path: string,
|
||||
version: string,
|
||||
|
@ -55,7 +55,7 @@ export class AdbScrcpyOptions1_16 extends AdbScrcpyOptionsBase<ScrcpyOptionsInit
|
|||
return encoders;
|
||||
}
|
||||
|
||||
public static async getDisplays(
|
||||
static async getDisplays(
|
||||
adb: Adb,
|
||||
path: string,
|
||||
version: string,
|
||||
|
@ -90,7 +90,7 @@ export class AdbScrcpyOptions1_16 extends AdbScrcpyOptionsBase<ScrcpyOptionsInit
|
|||
}
|
||||
}
|
||||
|
||||
public override getEncoders(
|
||||
override getEncoders(
|
||||
adb: Adb,
|
||||
path: string,
|
||||
version: string,
|
||||
|
@ -98,7 +98,7 @@ export class AdbScrcpyOptions1_16 extends AdbScrcpyOptionsBase<ScrcpyOptionsInit
|
|||
return AdbScrcpyOptions1_16.getEncoders(adb, path, version, this);
|
||||
}
|
||||
|
||||
public override getDisplays(
|
||||
override getDisplays(
|
||||
adb: Adb,
|
||||
path: string,
|
||||
version: string,
|
||||
|
@ -106,7 +106,7 @@ export class AdbScrcpyOptions1_16 extends AdbScrcpyOptionsBase<ScrcpyOptionsInit
|
|||
return AdbScrcpyOptions1_16.getDisplays(adb, path, version, this);
|
||||
}
|
||||
|
||||
public override createConnection(adb: Adb): AdbScrcpyConnection {
|
||||
override createConnection(adb: Adb): AdbScrcpyConnection {
|
||||
return AdbScrcpyOptions1_16.createConnection(
|
||||
adb,
|
||||
{
|
||||
|
|
|
@ -11,7 +11,7 @@ import { AdbScrcpyOptions1_16 } from "./1_16.js";
|
|||
import { AdbScrcpyOptionsBase } from "./types.js";
|
||||
|
||||
export class AdbScrcpyOptions1_22 extends AdbScrcpyOptionsBase<ScrcpyOptionsInit1_22> {
|
||||
public override getEncoders(
|
||||
override getEncoders(
|
||||
adb: Adb,
|
||||
path: string,
|
||||
version: string,
|
||||
|
@ -19,7 +19,7 @@ export class AdbScrcpyOptions1_22 extends AdbScrcpyOptionsBase<ScrcpyOptionsInit
|
|||
return AdbScrcpyOptions1_16.getEncoders(adb, path, version, this);
|
||||
}
|
||||
|
||||
public override getDisplays(
|
||||
override getDisplays(
|
||||
adb: Adb,
|
||||
path: string,
|
||||
version: string,
|
||||
|
@ -27,7 +27,7 @@ export class AdbScrcpyOptions1_22 extends AdbScrcpyOptionsBase<ScrcpyOptionsInit
|
|||
return AdbScrcpyOptions1_16.getDisplays(adb, path, version, this);
|
||||
}
|
||||
|
||||
public override createConnection(adb: Adb): AdbScrcpyConnection {
|
||||
override createConnection(adb: Adb): AdbScrcpyConnection {
|
||||
return AdbScrcpyOptions1_16.createConnection(
|
||||
adb,
|
||||
{
|
||||
|
|
|
@ -13,7 +13,7 @@ import type { AdbScrcpyOptions } from "./types.js";
|
|||
import { AdbScrcpyOptionsBase } from "./types.js";
|
||||
|
||||
export class AdbScrcpyOptions2_0 extends AdbScrcpyOptionsBase<ScrcpyOptionsInit2_0> {
|
||||
public static async getEncoders(
|
||||
static async getEncoders(
|
||||
adb: Adb,
|
||||
path: string,
|
||||
version: string,
|
||||
|
@ -48,7 +48,7 @@ export class AdbScrcpyOptions2_0 extends AdbScrcpyOptionsBase<ScrcpyOptionsInit2
|
|||
}
|
||||
}
|
||||
|
||||
public override async getEncoders(
|
||||
override async getEncoders(
|
||||
adb: Adb,
|
||||
path: string,
|
||||
version: string,
|
||||
|
@ -56,7 +56,7 @@ export class AdbScrcpyOptions2_0 extends AdbScrcpyOptionsBase<ScrcpyOptionsInit2
|
|||
return AdbScrcpyOptions2_0.getEncoders(adb, path, version, this);
|
||||
}
|
||||
|
||||
public override getDisplays(
|
||||
override getDisplays(
|
||||
adb: Adb,
|
||||
path: string,
|
||||
version: string,
|
||||
|
@ -64,7 +64,7 @@ export class AdbScrcpyOptions2_0 extends AdbScrcpyOptionsBase<ScrcpyOptionsInit2
|
|||
return AdbScrcpyOptions1_16.getDisplays(adb, path, version, this);
|
||||
}
|
||||
|
||||
public override createConnection(adb: Adb): AdbScrcpyConnection {
|
||||
override createConnection(adb: Adb): AdbScrcpyConnection {
|
||||
return AdbScrcpyOptions1_16.createConnection(
|
||||
adb,
|
||||
{
|
||||
|
|
|
@ -12,7 +12,7 @@ import { AdbScrcpyOptions2_0 } from "./2_0.js";
|
|||
import { AdbScrcpyOptionsBase } from "./types.js";
|
||||
|
||||
export class AdbScrcpyOptions2_1 extends AdbScrcpyOptionsBase<ScrcpyOptionsInit2_1> {
|
||||
public override async getEncoders(
|
||||
override async getEncoders(
|
||||
adb: Adb,
|
||||
path: string,
|
||||
version: string,
|
||||
|
@ -20,7 +20,7 @@ export class AdbScrcpyOptions2_1 extends AdbScrcpyOptionsBase<ScrcpyOptionsInit2
|
|||
return AdbScrcpyOptions2_0.getEncoders(adb, path, version, this);
|
||||
}
|
||||
|
||||
public override getDisplays(
|
||||
override getDisplays(
|
||||
adb: Adb,
|
||||
path: string,
|
||||
version: string,
|
||||
|
@ -28,7 +28,7 @@ export class AdbScrcpyOptions2_1 extends AdbScrcpyOptionsBase<ScrcpyOptionsInit2
|
|||
return AdbScrcpyOptions1_16.getDisplays(adb, path, version, this);
|
||||
}
|
||||
|
||||
public override createConnection(adb: Adb): AdbScrcpyConnection {
|
||||
override createConnection(adb: Adb): AdbScrcpyConnection {
|
||||
return AdbScrcpyOptions1_16.createConnection(
|
||||
adb,
|
||||
{
|
||||
|
|
|
@ -34,31 +34,31 @@ export abstract class AdbScrcpyOptionsBase<T extends object>
|
|||
extends ScrcpyOptionsBase<T, ScrcpyOptions<T>>
|
||||
implements AdbScrcpyOptions<T>
|
||||
{
|
||||
public override get defaults(): Required<T> {
|
||||
override get defaults(): Required<T> {
|
||||
return this._base.defaults;
|
||||
}
|
||||
|
||||
public tunnelForwardOverride = false;
|
||||
tunnelForwardOverride = false;
|
||||
|
||||
public constructor(base: ScrcpyOptions<T>) {
|
||||
constructor(base: ScrcpyOptions<T>) {
|
||||
super(base, base.value);
|
||||
}
|
||||
|
||||
public serialize(): string[] {
|
||||
serialize(): string[] {
|
||||
return this._base.serialize();
|
||||
}
|
||||
|
||||
public abstract getEncoders(
|
||||
abstract getEncoders(
|
||||
adb: Adb,
|
||||
path: string,
|
||||
version: string,
|
||||
): Promise<ScrcpyEncoder[]>;
|
||||
|
||||
public abstract getDisplays(
|
||||
abstract getDisplays(
|
||||
adb: Adb,
|
||||
path: string,
|
||||
version: string,
|
||||
): Promise<ScrcpyDisplay[]>;
|
||||
|
||||
public abstract createConnection(adb: Adb): AdbScrcpyConnection;
|
||||
abstract createConnection(adb: Adb): AdbScrcpyConnection;
|
||||
}
|
||||
|
|
|
@ -58,15 +58,15 @@ function nodeSocketToStreamPair(socket: Socket) {
|
|||
}
|
||||
|
||||
export class AdbServerNodeTcpConnection implements AdbServerConnection {
|
||||
public readonly spec: SocketConnectOpts;
|
||||
readonly spec: SocketConnectOpts;
|
||||
|
||||
private readonly _listeners = new Map<string, Server>();
|
||||
readonly #listeners = new Map<string, Server>();
|
||||
|
||||
public constructor(spec: SocketConnectOpts) {
|
||||
constructor(spec: SocketConnectOpts) {
|
||||
this.spec = spec;
|
||||
}
|
||||
|
||||
public async connect(
|
||||
async connect(
|
||||
{ unref }: AdbServerConnectionOptions = { unref: false }
|
||||
): Promise<ReadableWritablePair<Uint8Array, Uint8Array>> {
|
||||
const socket = new Socket();
|
||||
|
@ -81,7 +81,7 @@ export class AdbServerNodeTcpConnection implements AdbServerConnection {
|
|||
return nodeSocketToStreamPair(socket);
|
||||
}
|
||||
|
||||
public async addReverseTunnel(
|
||||
async addReverseTunnel(
|
||||
handler: AdbIncomingSocketHandler,
|
||||
address?: string
|
||||
): Promise<string> {
|
||||
|
@ -127,23 +127,23 @@ export class AdbServerNodeTcpConnection implements AdbServerConnection {
|
|||
address = `tcp:${info.address}:${info.port}`;
|
||||
}
|
||||
|
||||
this._listeners.set(address, server);
|
||||
this.#listeners.set(address, server);
|
||||
return address;
|
||||
}
|
||||
|
||||
removeReverseTunnel(address: string): ValueOrPromise<void> {
|
||||
const server = this._listeners.get(address);
|
||||
const server = this.#listeners.get(address);
|
||||
if (!server) {
|
||||
return;
|
||||
}
|
||||
server.close();
|
||||
this._listeners.delete(address);
|
||||
this.#listeners.delete(address);
|
||||
}
|
||||
|
||||
clearReverseTunnels(): ValueOrPromise<void> {
|
||||
for (const server of this._listeners.values()) {
|
||||
for (const server of this.#listeners.values()) {
|
||||
server.close();
|
||||
}
|
||||
this._listeners.clear();
|
||||
this.#listeners.clear();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -51,30 +51,30 @@ export interface AdbTransport extends Closeable {
|
|||
}
|
||||
|
||||
export class Adb implements Closeable {
|
||||
public readonly transport: AdbTransport;
|
||||
readonly transport: AdbTransport;
|
||||
|
||||
public get serial() {
|
||||
get serial() {
|
||||
return this.transport.serial;
|
||||
}
|
||||
|
||||
public get maxPayloadSize() {
|
||||
get maxPayloadSize() {
|
||||
return this.transport.maxPayloadSize;
|
||||
}
|
||||
|
||||
public get banner() {
|
||||
get banner() {
|
||||
return this.transport.banner;
|
||||
}
|
||||
|
||||
public get disconnected() {
|
||||
get disconnected() {
|
||||
return this.transport.disconnected;
|
||||
}
|
||||
|
||||
public readonly subprocess: AdbSubprocess;
|
||||
public readonly power: AdbPower;
|
||||
public readonly reverse: AdbReverseCommand;
|
||||
public readonly tcpip: AdbTcpIpCommand;
|
||||
readonly subprocess: AdbSubprocess;
|
||||
readonly power: AdbPower;
|
||||
readonly reverse: AdbReverseCommand;
|
||||
readonly tcpip: AdbTcpIpCommand;
|
||||
|
||||
public constructor(transport: AdbTransport) {
|
||||
constructor(transport: AdbTransport) {
|
||||
this.transport = transport;
|
||||
|
||||
this.subprocess = new AdbSubprocess(this);
|
||||
|
@ -83,22 +83,22 @@ export class Adb implements Closeable {
|
|||
this.tcpip = new AdbTcpIpCommand(this);
|
||||
}
|
||||
|
||||
public supportsFeature(feature: AdbFeature): boolean {
|
||||
supportsFeature(feature: AdbFeature): boolean {
|
||||
return this.banner.features.includes(feature);
|
||||
}
|
||||
|
||||
public async createSocket(service: string): Promise<AdbSocket> {
|
||||
async createSocket(service: string): Promise<AdbSocket> {
|
||||
return this.transport.connect(service);
|
||||
}
|
||||
|
||||
public async createSocketAndWait(service: string): Promise<string> {
|
||||
async createSocketAndWait(service: string): Promise<string> {
|
||||
const socket = await this.createSocket(service);
|
||||
return await socket.readable
|
||||
.pipeThrough(new DecodeUtf8Stream())
|
||||
.pipeThrough(new ConcatStringStream());
|
||||
}
|
||||
|
||||
public async getProp(key: string): Promise<string> {
|
||||
async getProp(key: string): Promise<string> {
|
||||
const stdout = await this.subprocess.spawnAndWaitLegacy([
|
||||
"getprop",
|
||||
key,
|
||||
|
@ -106,7 +106,7 @@ export class Adb implements Closeable {
|
|||
return stdout.trim();
|
||||
}
|
||||
|
||||
public async rm(...filenames: string[]): Promise<string> {
|
||||
async rm(...filenames: string[]): Promise<string> {
|
||||
// https://android.googlesource.com/platform/packages/modules/adb/+/1a0fb8846d4e6b671c8aa7f137a8c21d7b248716/client/adb_install.cpp#984
|
||||
const stdout = await this.subprocess.spawnAndWaitLegacy([
|
||||
"rm",
|
||||
|
@ -116,16 +116,16 @@ export class Adb implements Closeable {
|
|||
return stdout;
|
||||
}
|
||||
|
||||
public async sync(): Promise<AdbSync> {
|
||||
async sync(): Promise<AdbSync> {
|
||||
const socket = await this.createSocket("sync:");
|
||||
return new AdbSync(this, socket);
|
||||
}
|
||||
|
||||
public async framebuffer(): Promise<AdbFrameBuffer> {
|
||||
async framebuffer(): Promise<AdbFrameBuffer> {
|
||||
return framebuffer(this);
|
||||
}
|
||||
|
||||
public async close(): Promise<void> {
|
||||
async close(): Promise<void> {
|
||||
await this.transport.close();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ export enum AdbBannerKey {
|
|||
}
|
||||
|
||||
export class AdbBanner {
|
||||
public static parse(banner: string) {
|
||||
static parse(banner: string) {
|
||||
let product: string | undefined;
|
||||
let model: string | undefined;
|
||||
let device: string | undefined;
|
||||
|
@ -49,26 +49,26 @@ export class AdbBanner {
|
|||
}
|
||||
|
||||
#product: string | undefined;
|
||||
public get product() {
|
||||
get product() {
|
||||
return this.#product;
|
||||
}
|
||||
|
||||
#model: string | undefined;
|
||||
public get model() {
|
||||
get model() {
|
||||
return this.#model;
|
||||
}
|
||||
|
||||
#device: string | undefined;
|
||||
public get device() {
|
||||
get device() {
|
||||
return this.#device;
|
||||
}
|
||||
|
||||
#features: AdbFeature[] = [];
|
||||
public get features() {
|
||||
get features() {
|
||||
return this.#features;
|
||||
}
|
||||
|
||||
public constructor(
|
||||
constructor(
|
||||
product: string | undefined,
|
||||
model: string | undefined,
|
||||
device: string | undefined,
|
||||
|
|
|
@ -5,7 +5,7 @@ import type { Adb } from "../adb.js";
|
|||
export class AdbCommandBase extends AutoDisposable {
|
||||
protected adb: Adb;
|
||||
|
||||
public constructor(adb: Adb) {
|
||||
constructor(adb: Adb) {
|
||||
super();
|
||||
this.adb = adb;
|
||||
}
|
||||
|
|
|
@ -6,23 +6,23 @@
|
|||
import { AdbCommandBase } from "./base.js";
|
||||
|
||||
export class AdbPower extends AdbCommandBase {
|
||||
public reboot(mode = "") {
|
||||
reboot(mode = "") {
|
||||
return this.adb.createSocketAndWait(`reboot:${mode}`);
|
||||
}
|
||||
|
||||
public bootloader() {
|
||||
bootloader() {
|
||||
return this.reboot("bootloader");
|
||||
}
|
||||
|
||||
public fastboot() {
|
||||
fastboot() {
|
||||
return this.reboot("fastboot");
|
||||
}
|
||||
|
||||
public recovery() {
|
||||
recovery() {
|
||||
return this.reboot("recovery");
|
||||
}
|
||||
|
||||
public sideload() {
|
||||
sideload() {
|
||||
return this.reboot("sideload");
|
||||
}
|
||||
|
||||
|
@ -31,15 +31,15 @@ export class AdbPower extends AdbCommandBase {
|
|||
*
|
||||
* Only works on some Qualcomm devices.
|
||||
*/
|
||||
public qualcommEdlMode() {
|
||||
qualcommEdlMode() {
|
||||
return this.reboot("edl");
|
||||
}
|
||||
|
||||
public powerOff() {
|
||||
powerOff() {
|
||||
return this.adb.subprocess.spawnAndWaitLegacy(["reboot", "-p"]);
|
||||
}
|
||||
|
||||
public powerButton(longPress = false) {
|
||||
powerButton(longPress = false) {
|
||||
return this.adb.subprocess.spawnAndWaitLegacy([
|
||||
"input",
|
||||
"keyevent",
|
||||
|
@ -52,7 +52,7 @@ export class AdbPower extends AdbCommandBase {
|
|||
*
|
||||
* Only works on Samsung devices.
|
||||
*/
|
||||
public samsungOdin() {
|
||||
samsungOdin() {
|
||||
return this.reboot("download");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,14 +20,14 @@ const AdbReverseStringResponse = new Struct()
|
|||
.string("content", { lengthField: "length", lengthFieldRadix: 16 });
|
||||
|
||||
export class AdbReverseError extends Error {
|
||||
public constructor(message: string) {
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
Object.setPrototypeOf(this, new.target.prototype);
|
||||
}
|
||||
}
|
||||
|
||||
export class AdbReverseNotSupportedError extends AdbReverseError {
|
||||
public constructor() {
|
||||
constructor() {
|
||||
super(
|
||||
"ADB reverse tunnel is not supported on this device when connected wirelessly.",
|
||||
);
|
||||
|
@ -57,7 +57,7 @@ export class AdbReverseCommand extends AutoDisposable {
|
|||
|
||||
readonly #deviceAddressToLocalAddress = new Map<string, string>();
|
||||
|
||||
public constructor(adb: Adb) {
|
||||
constructor(adb: Adb) {
|
||||
super();
|
||||
|
||||
this.adb = adb;
|
||||
|
@ -77,7 +77,7 @@ export class AdbReverseCommand extends AutoDisposable {
|
|||
return stream;
|
||||
}
|
||||
|
||||
public async list(): Promise<AdbForwardListener[]> {
|
||||
async list(): Promise<AdbForwardListener[]> {
|
||||
const stream = await this.createBufferedStream("reverse:list-forward");
|
||||
|
||||
const response = await AdbReverseStringResponse.deserialize(stream);
|
||||
|
@ -99,7 +99,7 @@ export class AdbReverseCommand extends AutoDisposable {
|
|||
* @param localAddress The address that listens on the local machine.
|
||||
* @returns `tcp:{ACTUAL_LISTENING_PORT}`, If `deviceAddress` is `tcp:0`; otherwise, `deviceAddress`.
|
||||
*/
|
||||
public async addExternal(deviceAddress: string, localAddress: string) {
|
||||
async addExternal(deviceAddress: string, localAddress: string) {
|
||||
const stream = await this.sendRequest(
|
||||
`reverse:forward:${deviceAddress};${localAddress}`,
|
||||
);
|
||||
|
@ -137,7 +137,7 @@ export class AdbReverseCommand extends AutoDisposable {
|
|||
* @throws {AdbReverseNotSupportedError} If ADB reverse tunnel is not supported on this device when connected wirelessly.
|
||||
* @throws {AdbReverseError} If ADB daemon returns an error.
|
||||
*/
|
||||
public async add(
|
||||
async add(
|
||||
deviceAddress: string,
|
||||
handler: AdbIncomingSocketHandler,
|
||||
localAddress?: string,
|
||||
|
@ -157,7 +157,7 @@ export class AdbReverseCommand extends AutoDisposable {
|
|||
}
|
||||
}
|
||||
|
||||
public async remove(deviceAddress: string): Promise<void> {
|
||||
async remove(deviceAddress: string): Promise<void> {
|
||||
const localAddress =
|
||||
this.#deviceAddressToLocalAddress.get(deviceAddress);
|
||||
if (localAddress) {
|
||||
|
@ -169,7 +169,7 @@ export class AdbReverseCommand extends AutoDisposable {
|
|||
// No need to close the stream, device will close it
|
||||
}
|
||||
|
||||
public async removeAll(): Promise<void> {
|
||||
async removeAll(): Promise<void> {
|
||||
await this.adb.transport.clearReverseTunnels();
|
||||
this.#deviceAddressToLocalAddress.clear();
|
||||
|
||||
|
|
|
@ -37,7 +37,7 @@ export interface AdbSubprocessWaitResult {
|
|||
}
|
||||
|
||||
export class AdbSubprocess extends AdbCommandBase {
|
||||
private async createProtocol(
|
||||
async #createProtocol(
|
||||
mode: "pty" | "raw",
|
||||
command?: string | string[],
|
||||
options?: Partial<AdbSubprocessOptions>,
|
||||
|
@ -75,11 +75,11 @@ export class AdbSubprocess extends AdbCommandBase {
|
|||
* @param options The options for creating the `AdbSubprocessProtocol`
|
||||
* @returns A new `AdbSubprocessProtocol` instance connecting to the spawned process.
|
||||
*/
|
||||
public shell(
|
||||
shell(
|
||||
command?: string | string[],
|
||||
options?: Partial<AdbSubprocessOptions>,
|
||||
): Promise<AdbSubprocessProtocol> {
|
||||
return this.createProtocol("pty", command, options);
|
||||
return this.#createProtocol("pty", command, options);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -91,11 +91,11 @@ export class AdbSubprocess extends AdbCommandBase {
|
|||
* @param options The options for creating the `AdbSubprocessProtocol`
|
||||
* @returns A new `AdbSubprocessProtocol` instance connecting to the spawned process.
|
||||
*/
|
||||
public spawn(
|
||||
spawn(
|
||||
command: string | string[],
|
||||
options?: Partial<AdbSubprocessOptions>,
|
||||
): Promise<AdbSubprocessProtocol> {
|
||||
return this.createProtocol("raw", command, options);
|
||||
return this.#createProtocol("raw", command, options);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -104,7 +104,7 @@ export class AdbSubprocess extends AdbCommandBase {
|
|||
* @param options The options for creating the `AdbSubprocessProtocol`
|
||||
* @returns The entire output of the command
|
||||
*/
|
||||
public async spawnAndWait(
|
||||
async spawnAndWait(
|
||||
command: string | string[],
|
||||
options?: Partial<AdbSubprocessOptions>,
|
||||
): Promise<AdbSubprocessWaitResult> {
|
||||
|
@ -132,9 +132,7 @@ export class AdbSubprocess extends AdbCommandBase {
|
|||
* @param command The command to run
|
||||
* @returns The entire output of the command
|
||||
*/
|
||||
public async spawnAndWaitLegacy(
|
||||
command: string | string[],
|
||||
): Promise<string> {
|
||||
async spawnAndWaitLegacy(command: string | string[]): Promise<string> {
|
||||
const { stdout } = await this.spawnAndWait(command, {
|
||||
protocols: [AdbSubprocessNoneProtocol],
|
||||
});
|
||||
|
|
|
@ -13,17 +13,17 @@ import type { AdbSubprocessProtocol } from "./types.js";
|
|||
* * `resize`: No
|
||||
*/
|
||||
export class AdbSubprocessNoneProtocol implements AdbSubprocessProtocol {
|
||||
public static isSupported() {
|
||||
static isSupported() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public static async pty(adb: Adb, command: string) {
|
||||
static async pty(adb: Adb, command: string) {
|
||||
return new AdbSubprocessNoneProtocol(
|
||||
await adb.createSocket(`shell:${command}`),
|
||||
);
|
||||
}
|
||||
|
||||
public static async raw(adb: Adb, command: string) {
|
||||
static async raw(adb: Adb, command: string) {
|
||||
// `shell,raw:${command}` also triggers raw mode,
|
||||
// But is not supported on Android version <7.
|
||||
return new AdbSubprocessNoneProtocol(
|
||||
|
@ -36,7 +36,7 @@ export class AdbSubprocessNoneProtocol implements AdbSubprocessProtocol {
|
|||
readonly #duplex: DuplexStreamFactory<Uint8Array, Uint8Array>;
|
||||
|
||||
// Legacy shell forwards all data to stdin.
|
||||
public get stdin() {
|
||||
get stdin() {
|
||||
return this.#socket.writable;
|
||||
}
|
||||
|
||||
|
@ -44,7 +44,7 @@ export class AdbSubprocessNoneProtocol implements AdbSubprocessProtocol {
|
|||
/**
|
||||
* Legacy shell mixes stdout and stderr.
|
||||
*/
|
||||
public get stdout() {
|
||||
get stdout() {
|
||||
return this.#stdout;
|
||||
}
|
||||
|
||||
|
@ -52,16 +52,16 @@ export class AdbSubprocessNoneProtocol implements AdbSubprocessProtocol {
|
|||
/**
|
||||
* `stderr` will always be empty.
|
||||
*/
|
||||
public get stderr() {
|
||||
get stderr() {
|
||||
return this.#stderr;
|
||||
}
|
||||
|
||||
#exit: Promise<number>;
|
||||
public get exit() {
|
||||
get exit() {
|
||||
return this.#exit;
|
||||
}
|
||||
|
||||
public constructor(socket: AdbSocket) {
|
||||
constructor(socket: AdbSocket) {
|
||||
this.#socket = socket;
|
||||
|
||||
// Link `stdout`, `stderr` and `stdin` together,
|
||||
|
@ -77,11 +77,11 @@ export class AdbSubprocessNoneProtocol implements AdbSubprocessProtocol {
|
|||
this.#exit = this.#duplex.closed.then(() => 0);
|
||||
}
|
||||
|
||||
public resize() {
|
||||
resize() {
|
||||
// Not supported, but don't throw.
|
||||
}
|
||||
|
||||
public kill() {
|
||||
kill() {
|
||||
return this.#duplex.close();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -61,38 +61,38 @@ class StdinSerializeStream extends ConsumableTransformStream<
|
|||
}
|
||||
|
||||
class MultiplexStream<T> {
|
||||
private _readable: PushReadableStream<T>;
|
||||
private _readableController!: PushReadableStreamController<T>;
|
||||
public get readable() {
|
||||
return this._readable;
|
||||
#readable: PushReadableStream<T>;
|
||||
#readableController!: PushReadableStreamController<T>;
|
||||
get readable() {
|
||||
return this.#readable;
|
||||
}
|
||||
|
||||
private _activeCount = 0;
|
||||
#activeCount = 0;
|
||||
|
||||
constructor() {
|
||||
this._readable = new PushReadableStream((controller) => {
|
||||
this._readableController = controller;
|
||||
this.#readable = new PushReadableStream((controller) => {
|
||||
this.#readableController = controller;
|
||||
});
|
||||
}
|
||||
|
||||
public createWriteable() {
|
||||
createWriteable() {
|
||||
return new WritableStream<T>({
|
||||
start: () => {
|
||||
this._activeCount += 1;
|
||||
this.#activeCount += 1;
|
||||
},
|
||||
write: async (chunk) => {
|
||||
await this._readableController.enqueue(chunk);
|
||||
await this.#readableController.enqueue(chunk);
|
||||
},
|
||||
abort: () => {
|
||||
this._activeCount -= 1;
|
||||
if (this._activeCount === 0) {
|
||||
this._readableController.close();
|
||||
this.#activeCount -= 1;
|
||||
if (this.#activeCount === 0) {
|
||||
this.#readableController.close();
|
||||
}
|
||||
},
|
||||
close: () => {
|
||||
this._activeCount -= 1;
|
||||
if (this._activeCount === 0) {
|
||||
this._readableController.close();
|
||||
this.#activeCount -= 1;
|
||||
if (this.#activeCount === 0) {
|
||||
this.#readableController.close();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
@ -108,18 +108,18 @@ class MultiplexStream<T> {
|
|||
* * `resize`: Yes
|
||||
*/
|
||||
export class AdbSubprocessShellProtocol implements AdbSubprocessProtocol {
|
||||
public static isSupported(adb: Adb) {
|
||||
static isSupported(adb: Adb) {
|
||||
return adb.supportsFeature(AdbFeature.ShellV2);
|
||||
}
|
||||
|
||||
public static async pty(adb: Adb, command: string) {
|
||||
static async pty(adb: Adb, command: string) {
|
||||
// TODO: AdbShellSubprocessProtocol: Support setting `XTERM` environment variable
|
||||
return new AdbSubprocessShellProtocol(
|
||||
await adb.createSocket(`shell,v2,pty:${command}`),
|
||||
);
|
||||
}
|
||||
|
||||
public static async raw(adb: Adb, command: string) {
|
||||
static async raw(adb: Adb, command: string) {
|
||||
return new AdbSubprocessShellProtocol(
|
||||
await adb.createSocket(`shell,v2,raw:${command}`),
|
||||
);
|
||||
|
@ -131,26 +131,26 @@ export class AdbSubprocessShellProtocol implements AdbSubprocessProtocol {
|
|||
>;
|
||||
|
||||
#stdin: WritableStream<Consumable<Uint8Array>>;
|
||||
public get stdin() {
|
||||
get stdin() {
|
||||
return this.#stdin;
|
||||
}
|
||||
|
||||
#stdout: ReadableStream<Uint8Array>;
|
||||
public get stdout() {
|
||||
get stdout() {
|
||||
return this.#stdout;
|
||||
}
|
||||
|
||||
#stderr: ReadableStream<Uint8Array>;
|
||||
public get stderr() {
|
||||
get stderr() {
|
||||
return this.#stderr;
|
||||
}
|
||||
|
||||
readonly #exit = new PromiseResolver<number>();
|
||||
public get exit() {
|
||||
get exit() {
|
||||
return this.#exit.promise;
|
||||
}
|
||||
|
||||
public constructor(socket: AdbSocket) {
|
||||
constructor(socket: AdbSocket) {
|
||||
this.#socket = socket;
|
||||
|
||||
// Check this image to help you understand the stream graph
|
||||
|
@ -225,7 +225,7 @@ export class AdbSubprocessShellProtocol implements AdbSubprocessProtocol {
|
|||
this.#socketWriter = multiplexer.createWriteable().getWriter();
|
||||
}
|
||||
|
||||
public async resize(rows: number, cols: number) {
|
||||
async resize(rows: number, cols: number) {
|
||||
await ConsumableWritableStream.write(this.#socketWriter, {
|
||||
id: AdbShellProtocolId.WindowSizeChange,
|
||||
data: encodeUtf8(
|
||||
|
@ -237,7 +237,7 @@ export class AdbSubprocessShellProtocol implements AdbSubprocessProtocol {
|
|||
});
|
||||
}
|
||||
|
||||
public kill() {
|
||||
kill() {
|
||||
return this.#socket.close();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,11 +19,11 @@ export class AdbSyncSocketLocked implements AsyncExactReadable {
|
|||
readonly #writeLock = new AutoResetEvent();
|
||||
readonly #combiner: BufferCombiner;
|
||||
|
||||
public get position() {
|
||||
get position() {
|
||||
return this.#readable.position;
|
||||
}
|
||||
|
||||
public constructor(
|
||||
constructor(
|
||||
writer: WritableStreamDefaultWriter<Consumable<Uint8Array>>,
|
||||
readable: BufferedReadableStream,
|
||||
bufferSize: number,
|
||||
|
@ -35,39 +35,39 @@ export class AdbSyncSocketLocked implements AsyncExactReadable {
|
|||
this.#combiner = new BufferCombiner(bufferSize);
|
||||
}
|
||||
|
||||
private async writeInnerStream(buffer: Uint8Array) {
|
||||
async #writeInnerStream(buffer: Uint8Array) {
|
||||
await ConsumableWritableStream.write(this.#writer, buffer);
|
||||
}
|
||||
|
||||
public async flush() {
|
||||
async flush() {
|
||||
try {
|
||||
await this.#writeLock.wait();
|
||||
const buffer = this.#combiner.flush();
|
||||
if (buffer) {
|
||||
await this.writeInnerStream(buffer);
|
||||
await this.#writeInnerStream(buffer);
|
||||
}
|
||||
} finally {
|
||||
this.#writeLock.notifyOne();
|
||||
}
|
||||
}
|
||||
|
||||
public async write(data: Uint8Array) {
|
||||
async write(data: Uint8Array) {
|
||||
try {
|
||||
await this.#writeLock.wait();
|
||||
for (const buffer of this.#combiner.push(data)) {
|
||||
await this.writeInnerStream(buffer);
|
||||
await this.#writeInnerStream(buffer);
|
||||
}
|
||||
} finally {
|
||||
this.#writeLock.notifyOne();
|
||||
}
|
||||
}
|
||||
|
||||
public async readExactly(length: number) {
|
||||
async readExactly(length: number) {
|
||||
await this.flush();
|
||||
return await this.#readable.readExactly(length);
|
||||
}
|
||||
|
||||
public release(): void {
|
||||
release(): void {
|
||||
this.#combiner.flush();
|
||||
this.#socketLock.notifyOne();
|
||||
}
|
||||
|
@ -78,7 +78,7 @@ export class AdbSyncSocket {
|
|||
readonly #socket: AdbSocket;
|
||||
readonly #locked: AdbSyncSocketLocked;
|
||||
|
||||
public constructor(socket: AdbSocket, bufferSize: number) {
|
||||
constructor(socket: AdbSocket, bufferSize: number) {
|
||||
this.#socket = socket;
|
||||
this.#locked = new AdbSyncSocketLocked(
|
||||
socket.writable.getWriter(),
|
||||
|
@ -88,12 +88,12 @@ export class AdbSyncSocket {
|
|||
);
|
||||
}
|
||||
|
||||
public async lock() {
|
||||
async lock() {
|
||||
await this.#lock.wait();
|
||||
return this.#locked;
|
||||
}
|
||||
|
||||
public async close() {
|
||||
async close() {
|
||||
await this.#socket.close();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -48,27 +48,27 @@ export class AdbSync extends AutoDisposable {
|
|||
readonly #supportsSendReceiveV2: boolean;
|
||||
readonly #needPushMkdirWorkaround: boolean;
|
||||
|
||||
public get supportsStat(): boolean {
|
||||
get supportsStat(): boolean {
|
||||
return this.#supportsStat;
|
||||
}
|
||||
|
||||
public get supportsListV2(): boolean {
|
||||
get supportsListV2(): boolean {
|
||||
return this.#supportsListV2;
|
||||
}
|
||||
|
||||
public get fixedPushMkdir(): boolean {
|
||||
get fixedPushMkdir(): boolean {
|
||||
return this.#fixedPushMkdir;
|
||||
}
|
||||
|
||||
public get supportsSendReceiveV2(): boolean {
|
||||
get supportsSendReceiveV2(): boolean {
|
||||
return this.#supportsSendReceiveV2;
|
||||
}
|
||||
|
||||
public get needPushMkdirWorkaround(): boolean {
|
||||
get needPushMkdirWorkaround(): boolean {
|
||||
return this.#needPushMkdirWorkaround;
|
||||
}
|
||||
|
||||
public constructor(adb: Adb, socket: AdbSocket) {
|
||||
constructor(adb: Adb, socket: AdbSocket) {
|
||||
super();
|
||||
|
||||
this._adb = adb;
|
||||
|
@ -86,11 +86,11 @@ export class AdbSync extends AutoDisposable {
|
|||
!this.fixedPushMkdir;
|
||||
}
|
||||
|
||||
public async lstat(path: string): Promise<AdbSyncStat> {
|
||||
async lstat(path: string): Promise<AdbSyncStat> {
|
||||
return await adbSyncLstat(this._socket, path, this.supportsStat);
|
||||
}
|
||||
|
||||
public async stat(path: string) {
|
||||
async stat(path: string) {
|
||||
if (!this.supportsStat) {
|
||||
throw new Error("Not supported");
|
||||
}
|
||||
|
@ -98,7 +98,7 @@ export class AdbSync extends AutoDisposable {
|
|||
return await adbSyncStat(this._socket, path);
|
||||
}
|
||||
|
||||
public async isDirectory(path: string): Promise<boolean> {
|
||||
async isDirectory(path: string): Promise<boolean> {
|
||||
try {
|
||||
await this.lstat(path + "/");
|
||||
return true;
|
||||
|
@ -107,11 +107,11 @@ export class AdbSync extends AutoDisposable {
|
|||
}
|
||||
}
|
||||
|
||||
public opendir(path: string): AsyncGenerator<AdbSyncEntry, void, void> {
|
||||
opendir(path: string): AsyncGenerator<AdbSyncEntry, void, void> {
|
||||
return adbSyncOpenDir(this._socket, path, this.supportsListV2);
|
||||
}
|
||||
|
||||
public async readdir(path: string) {
|
||||
async readdir(path: string) {
|
||||
const results: AdbSyncEntry[] = [];
|
||||
for await (const entry of this.opendir(path)) {
|
||||
results.push(entry);
|
||||
|
@ -125,7 +125,7 @@ export class AdbSync extends AutoDisposable {
|
|||
* @param filename The full path of the file on device to read.
|
||||
* @returns A `ReadableStream` that contains the file content.
|
||||
*/
|
||||
public read(filename: string): ReadableStream<Uint8Array> {
|
||||
read(filename: string): ReadableStream<Uint8Array> {
|
||||
return adbSyncPull(this._socket, filename);
|
||||
}
|
||||
|
||||
|
@ -134,7 +134,7 @@ export class AdbSync extends AutoDisposable {
|
|||
*
|
||||
* @param options The content and options of the file to write.
|
||||
*/
|
||||
public async write(options: AdbSyncWriteOptions): Promise<void> {
|
||||
async write(options: AdbSyncWriteOptions): Promise<void> {
|
||||
if (this.needPushMkdirWorkaround) {
|
||||
// It may fail if the path is already existed.
|
||||
// Ignore the result.
|
||||
|
@ -153,7 +153,7 @@ export class AdbSync extends AutoDisposable {
|
|||
});
|
||||
}
|
||||
|
||||
public override async dispose() {
|
||||
override async dispose() {
|
||||
super.dispose();
|
||||
await this._socket.close();
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { AdbCommandBase } from "./base.js";
|
||||
|
||||
export class AdbTcpIpCommand extends AdbCommandBase {
|
||||
public async setPort(port: number): Promise<string> {
|
||||
async setPort(port: number): Promise<string> {
|
||||
if (port <= 0) {
|
||||
throw new Error(`Invalid port ${port}`);
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ export class AdbTcpIpCommand extends AdbCommandBase {
|
|||
return output;
|
||||
}
|
||||
|
||||
public async disable(): Promise<string> {
|
||||
async disable(): Promise<string> {
|
||||
const output = await this.adb.createSocketAndWait("usb:");
|
||||
if (output !== "restarting in USB mode\n") {
|
||||
throw new Error(output);
|
||||
|
|
|
@ -120,34 +120,30 @@ export const ADB_DEFAULT_AUTHENTICATORS: AdbAuthenticator[] = [
|
|||
];
|
||||
|
||||
export class AdbAuthenticationProcessor implements Disposable {
|
||||
public readonly authenticators: readonly AdbAuthenticator[];
|
||||
readonly authenticators: readonly AdbAuthenticator[];
|
||||
|
||||
private readonly credentialStore: AdbCredentialStore;
|
||||
readonly #credentialStore: AdbCredentialStore;
|
||||
|
||||
#pendingRequest = new PromiseResolver<AdbPacketData>();
|
||||
#iterator: AsyncIterator<AdbPacketData, void, void> | undefined;
|
||||
|
||||
public constructor(
|
||||
constructor(
|
||||
authenticators: readonly AdbAuthenticator[],
|
||||
credentialStore: AdbCredentialStore,
|
||||
) {
|
||||
this.authenticators = authenticators;
|
||||
this.credentialStore = credentialStore;
|
||||
this.#credentialStore = credentialStore;
|
||||
}
|
||||
|
||||
private getNextRequest = (): Promise<AdbPacketData> => {
|
||||
#getNextRequest = (): Promise<AdbPacketData> => {
|
||||
return this.#pendingRequest.promise;
|
||||
};
|
||||
|
||||
private async *invokeAuthenticator(): AsyncGenerator<
|
||||
AdbPacketData,
|
||||
void,
|
||||
void
|
||||
> {
|
||||
async *#invokeAuthenticator(): AsyncGenerator<AdbPacketData, void, void> {
|
||||
for (const authenticator of this.authenticators) {
|
||||
for await (const packet of authenticator(
|
||||
this.credentialStore,
|
||||
this.getNextRequest,
|
||||
this.#credentialStore,
|
||||
this.#getNextRequest,
|
||||
)) {
|
||||
// If the authenticator yielded a response
|
||||
// Prepare `nextRequest` for next authentication request
|
||||
|
@ -162,9 +158,9 @@ export class AdbAuthenticationProcessor implements Disposable {
|
|||
}
|
||||
}
|
||||
|
||||
public async process(packet: AdbPacketData): Promise<AdbPacketData> {
|
||||
async process(packet: AdbPacketData): Promise<AdbPacketData> {
|
||||
if (!this.#iterator) {
|
||||
this.#iterator = this.invokeAuthenticator();
|
||||
this.#iterator = this.#invokeAuthenticator();
|
||||
}
|
||||
|
||||
this.#pendingRequest.resolve(packet);
|
||||
|
@ -177,7 +173,7 @@ export class AdbAuthenticationProcessor implements Disposable {
|
|||
return result.value;
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
dispose() {
|
||||
void this.#iterator?.return?.();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -51,18 +51,18 @@ export class AdbPacketDispatcher implements Closeable {
|
|||
|
||||
#writer: WritableStreamDefaultWriter<Consumable<AdbPacketInit>>;
|
||||
|
||||
public readonly options: AdbPacketDispatcherOptions;
|
||||
readonly options: AdbPacketDispatcherOptions;
|
||||
|
||||
#closed = false;
|
||||
#disconnected = new PromiseResolver<void>();
|
||||
public get disconnected() {
|
||||
get disconnected() {
|
||||
return this.#disconnected.promise;
|
||||
}
|
||||
|
||||
#incomingSocketHandlers = new Map<string, AdbIncomingSocketHandler>();
|
||||
#readAbortController = new AbortController();
|
||||
|
||||
public constructor(
|
||||
constructor(
|
||||
connection: ReadableWritablePair<
|
||||
AdbPacketData,
|
||||
Consumable<AdbPacketInit>
|
||||
|
@ -77,10 +77,10 @@ export class AdbPacketDispatcher implements Closeable {
|
|||
write: async (packet) => {
|
||||
switch (packet.command) {
|
||||
case AdbCommand.OK:
|
||||
this.handleOk(packet);
|
||||
this.#handleOk(packet);
|
||||
break;
|
||||
case AdbCommand.Close:
|
||||
await this.handleClose(packet);
|
||||
await this.#handleClose(packet);
|
||||
break;
|
||||
case AdbCommand.Write:
|
||||
if (this.#sockets.has(packet.arg1)) {
|
||||
|
@ -98,7 +98,7 @@ export class AdbPacketDispatcher implements Closeable {
|
|||
`Unknown local socket id: ${packet.arg1}`,
|
||||
);
|
||||
case AdbCommand.Open:
|
||||
await this.handleOpen(packet);
|
||||
await this.#handleOpen(packet);
|
||||
break;
|
||||
default:
|
||||
// Junk data may only appear in the authentication phase,
|
||||
|
@ -125,20 +125,20 @@ export class AdbPacketDispatcher implements Closeable {
|
|||
)
|
||||
.then(
|
||||
() => {
|
||||
this.dispose();
|
||||
this.#dispose();
|
||||
},
|
||||
(e) => {
|
||||
if (!this.#closed) {
|
||||
this.#disconnected.reject(e);
|
||||
}
|
||||
this.dispose();
|
||||
this.#dispose();
|
||||
},
|
||||
);
|
||||
|
||||
this.#writer = connection.writable.getWriter();
|
||||
}
|
||||
|
||||
private handleOk(packet: AdbPacketData) {
|
||||
#handleOk(packet: AdbPacketData) {
|
||||
if (this.#initializers.resolve(packet.arg1, packet.arg0)) {
|
||||
// Device successfully created the socket
|
||||
return;
|
||||
|
@ -156,7 +156,7 @@ export class AdbPacketDispatcher implements Closeable {
|
|||
void this.sendPacket(AdbCommand.Close, packet.arg1, packet.arg0);
|
||||
}
|
||||
|
||||
private async handleClose(packet: AdbPacketData) {
|
||||
async #handleClose(packet: AdbPacketData) {
|
||||
// If the socket is still pending
|
||||
if (
|
||||
packet.arg0 === 0 &&
|
||||
|
@ -201,22 +201,19 @@ export class AdbPacketDispatcher implements Closeable {
|
|||
// the device may also respond with two `CLSE` packets.
|
||||
}
|
||||
|
||||
public addReverseTunnel(
|
||||
service: string,
|
||||
handler: AdbIncomingSocketHandler,
|
||||
) {
|
||||
addReverseTunnel(service: string, handler: AdbIncomingSocketHandler) {
|
||||
this.#incomingSocketHandlers.set(service, handler);
|
||||
}
|
||||
|
||||
public removeReverseTunnel(address: string) {
|
||||
removeReverseTunnel(address: string) {
|
||||
this.#incomingSocketHandlers.delete(address);
|
||||
}
|
||||
|
||||
public clearReverseTunnels() {
|
||||
clearReverseTunnels() {
|
||||
this.#incomingSocketHandlers.clear();
|
||||
}
|
||||
|
||||
private async handleOpen(packet: AdbPacketData) {
|
||||
async #handleOpen(packet: AdbPacketData) {
|
||||
// `AsyncOperationManager` doesn't support skipping IDs
|
||||
// Use `add` + `resolve` to simulate this behavior
|
||||
const [localId] = this.#initializers.add<number>();
|
||||
|
@ -251,7 +248,7 @@ export class AdbPacketDispatcher implements Closeable {
|
|||
}
|
||||
}
|
||||
|
||||
public async createSocket(service: string): Promise<AdbSocket> {
|
||||
async createSocket(service: string): Promise<AdbSocket> {
|
||||
if (this.options.appendNullToServiceString) {
|
||||
service += "\0";
|
||||
}
|
||||
|
@ -273,7 +270,7 @@ export class AdbPacketDispatcher implements Closeable {
|
|||
return controller.socket;
|
||||
}
|
||||
|
||||
public async sendPacket(
|
||||
async sendPacket(
|
||||
command: AdbCommand,
|
||||
arg0: number,
|
||||
arg1: number,
|
||||
|
@ -299,7 +296,7 @@ export class AdbPacketDispatcher implements Closeable {
|
|||
});
|
||||
}
|
||||
|
||||
public async close() {
|
||||
async close() {
|
||||
// Send `CLSE` packets for all sockets
|
||||
await Promise.all(
|
||||
Array.from(this.#sockets.values(), (socket) => socket.close()),
|
||||
|
@ -315,7 +312,7 @@ export class AdbPacketDispatcher implements Closeable {
|
|||
// `pipe().then()` will call `dispose`
|
||||
}
|
||||
|
||||
private dispose() {
|
||||
#dispose() {
|
||||
for (const socket of this.#sockets.values()) {
|
||||
socket.dispose().catch(unreachable);
|
||||
}
|
||||
|
|
|
@ -53,7 +53,7 @@ export class AdbPacketSerializeStream extends ConsumableTransformStream<
|
|||
AdbPacketInit,
|
||||
Uint8Array
|
||||
> {
|
||||
public constructor() {
|
||||
constructor() {
|
||||
const headerBuffer = new Uint8Array(AdbPacketHeader.size);
|
||||
super({
|
||||
transform: async (chunk, controller) => {
|
||||
|
|
|
@ -42,23 +42,23 @@ export class AdbDaemonSocketController
|
|||
Closeable,
|
||||
Disposable
|
||||
{
|
||||
private readonly dispatcher!: AdbPacketDispatcher;
|
||||
readonly #dispatcher!: AdbPacketDispatcher;
|
||||
|
||||
public readonly localId!: number;
|
||||
public readonly remoteId!: number;
|
||||
public readonly localCreated!: boolean;
|
||||
public readonly service!: string;
|
||||
readonly localId!: number;
|
||||
readonly remoteId!: number;
|
||||
readonly localCreated!: boolean;
|
||||
readonly service!: string;
|
||||
|
||||
#duplex: DuplexStreamFactory<Uint8Array, Consumable<Uint8Array>>;
|
||||
|
||||
#readable: ReadableStream<Uint8Array>;
|
||||
#readableController!: PushReadableStreamController<Uint8Array>;
|
||||
public get readable() {
|
||||
get readable() {
|
||||
return this.#readable;
|
||||
}
|
||||
|
||||
#writePromise: PromiseResolver<void> | undefined;
|
||||
public readonly writable: WritableStream<Consumable<Uint8Array>>;
|
||||
readonly writable: WritableStream<Consumable<Uint8Array>>;
|
||||
|
||||
#closed = false;
|
||||
/**
|
||||
|
@ -66,17 +66,21 @@ export class AdbDaemonSocketController
|
|||
*
|
||||
* It's only used by dispatcher to avoid sending another `CLSE` packet to remote.
|
||||
*/
|
||||
public get closed() {
|
||||
get closed() {
|
||||
return this.#closed;
|
||||
}
|
||||
|
||||
private _socket: AdbDaemonSocket;
|
||||
public get socket() {
|
||||
return this._socket;
|
||||
#socket: AdbDaemonSocket;
|
||||
get socket() {
|
||||
return this.#socket;
|
||||
}
|
||||
|
||||
public constructor(options: AdbDaemonSocketConstructionOptions) {
|
||||
Object.assign(this, options);
|
||||
constructor(options: AdbDaemonSocketConstructionOptions) {
|
||||
this.#dispatcher = options.dispatcher;
|
||||
this.localId = options.localId;
|
||||
this.remoteId = options.remoteId;
|
||||
this.localCreated = options.localCreated;
|
||||
this.service = options.service;
|
||||
|
||||
// Check this image to help you understand the stream graph
|
||||
// cspell: disable-next-line
|
||||
|
@ -89,10 +93,10 @@ export class AdbDaemonSocketController
|
|||
close: async () => {
|
||||
this.#closed = true;
|
||||
|
||||
await this.dispatcher.sendPacket(
|
||||
await this.#dispatcher.sendPacket(
|
||||
AdbCommand.Close,
|
||||
this.localId,
|
||||
this.remoteId,
|
||||
this.remoteId
|
||||
);
|
||||
|
||||
// Don't `dispose` here, we need to wait for `CLSE` response packet.
|
||||
|
@ -114,8 +118,8 @@ export class AdbDaemonSocketController
|
|||
size(chunk) {
|
||||
return chunk.byteLength;
|
||||
},
|
||||
},
|
||||
),
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
this.writable = pipeFrom(
|
||||
|
@ -124,23 +128,23 @@ export class AdbDaemonSocketController
|
|||
write: async (chunk) => {
|
||||
// Wait for an ack packet
|
||||
this.#writePromise = new PromiseResolver();
|
||||
await this.dispatcher.sendPacket(
|
||||
await this.#dispatcher.sendPacket(
|
||||
AdbCommand.Write,
|
||||
this.localId,
|
||||
this.remoteId,
|
||||
chunk,
|
||||
chunk
|
||||
);
|
||||
await this.#writePromise.promise;
|
||||
},
|
||||
}),
|
||||
})
|
||||
),
|
||||
new DistributionStream(this.dispatcher.options.maxPayloadSize),
|
||||
new DistributionStream(this.#dispatcher.options.maxPayloadSize)
|
||||
);
|
||||
|
||||
this._socket = new AdbDaemonSocket(this);
|
||||
this.#socket = new AdbDaemonSocket(this);
|
||||
}
|
||||
|
||||
public async enqueue(data: Uint8Array) {
|
||||
async enqueue(data: Uint8Array) {
|
||||
// Consumer may abort the `ReadableStream` to close the socket,
|
||||
// it's OK to throw away further packets in this case.
|
||||
if (this.#readableController.abortSignal.aborted) {
|
||||
|
@ -150,15 +154,15 @@ export class AdbDaemonSocketController
|
|||
await this.#readableController.enqueue(data);
|
||||
}
|
||||
|
||||
public ack() {
|
||||
ack() {
|
||||
this.#writePromise?.resolve();
|
||||
}
|
||||
|
||||
public async close(): Promise<void> {
|
||||
async close(): Promise<void> {
|
||||
await this.#duplex.close();
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
dispose() {
|
||||
return this.#duplex.dispose();
|
||||
}
|
||||
}
|
||||
|
@ -178,35 +182,35 @@ export class AdbDaemonSocket
|
|||
{
|
||||
#controller: AdbDaemonSocketController;
|
||||
|
||||
public get localId(): number {
|
||||
get localId(): number {
|
||||
return this.#controller.localId;
|
||||
}
|
||||
public get remoteId(): number {
|
||||
get remoteId(): number {
|
||||
return this.#controller.remoteId;
|
||||
}
|
||||
public get localCreated(): boolean {
|
||||
get localCreated(): boolean {
|
||||
return this.#controller.localCreated;
|
||||
}
|
||||
public get service(): string {
|
||||
get service(): string {
|
||||
return this.#controller.service;
|
||||
}
|
||||
|
||||
public get readable(): ReadableStream<Uint8Array> {
|
||||
get readable(): ReadableStream<Uint8Array> {
|
||||
return this.#controller.readable;
|
||||
}
|
||||
public get writable(): WritableStream<Consumable<Uint8Array>> {
|
||||
get writable(): WritableStream<Consumable<Uint8Array>> {
|
||||
return this.#controller.writable;
|
||||
}
|
||||
|
||||
public get closed(): boolean {
|
||||
get closed(): boolean {
|
||||
return this.#controller.closed;
|
||||
}
|
||||
|
||||
public constructor(controller: AdbDaemonSocketController) {
|
||||
constructor(controller: AdbDaemonSocketController) {
|
||||
this.#controller = controller;
|
||||
}
|
||||
|
||||
public close() {
|
||||
close() {
|
||||
return this.#controller.close();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -51,7 +51,7 @@ export class AdbDaemonTransport implements AdbTransport {
|
|||
* on the same connection. Because every time the device receives a `CNXN` packet,
|
||||
* it resets all internal state, and starts a new authentication process.
|
||||
*/
|
||||
public static async authenticate({
|
||||
static async authenticate({
|
||||
serial,
|
||||
connection,
|
||||
credentialStore,
|
||||
|
@ -186,30 +186,30 @@ export class AdbDaemonTransport implements AdbTransport {
|
|||
readonly #dispatcher: AdbPacketDispatcher;
|
||||
|
||||
#serial: string;
|
||||
public get serial() {
|
||||
get serial() {
|
||||
return this.#serial;
|
||||
}
|
||||
|
||||
#protocolVersion: number;
|
||||
public get protocolVersion() {
|
||||
get protocolVersion() {
|
||||
return this.#protocolVersion;
|
||||
}
|
||||
|
||||
#maxPayloadSize: number;
|
||||
public get maxPayloadSize() {
|
||||
get maxPayloadSize() {
|
||||
return this.#maxPayloadSize;
|
||||
}
|
||||
|
||||
#banner: AdbBanner;
|
||||
public get banner() {
|
||||
get banner() {
|
||||
return this.#banner;
|
||||
}
|
||||
|
||||
public get disconnected() {
|
||||
get disconnected() {
|
||||
return this.#dispatcher.disconnected;
|
||||
}
|
||||
|
||||
public constructor({
|
||||
constructor({
|
||||
serial,
|
||||
connection,
|
||||
version,
|
||||
|
@ -239,11 +239,11 @@ export class AdbDaemonTransport implements AdbTransport {
|
|||
this.#maxPayloadSize = maxPayloadSize;
|
||||
}
|
||||
|
||||
public connect(service: string): ValueOrPromise<AdbSocket> {
|
||||
connect(service: string): ValueOrPromise<AdbSocket> {
|
||||
return this.#dispatcher.createSocket(service);
|
||||
}
|
||||
|
||||
public addReverseTunnel(
|
||||
addReverseTunnel(
|
||||
handler: AdbIncomingSocketHandler,
|
||||
address?: string,
|
||||
): string {
|
||||
|
@ -255,15 +255,15 @@ export class AdbDaemonTransport implements AdbTransport {
|
|||
return address;
|
||||
}
|
||||
|
||||
public removeReverseTunnel(address: string): void {
|
||||
removeReverseTunnel(address: string): void {
|
||||
this.#dispatcher.removeReverseTunnel(address);
|
||||
}
|
||||
|
||||
public clearReverseTunnels(): void {
|
||||
clearReverseTunnels(): void {
|
||||
this.#dispatcher.clearReverseTunnels();
|
||||
}
|
||||
|
||||
public close(): ValueOrPromise<void> {
|
||||
close(): ValueOrPromise<void> {
|
||||
return this.#dispatcher.close();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -74,17 +74,17 @@ export interface AdbServerDevice {
|
|||
}
|
||||
|
||||
export class AdbServerClient {
|
||||
public static readonly VERSION = 41;
|
||||
static readonly VERSION = 41;
|
||||
|
||||
public readonly connection: AdbServerConnection;
|
||||
readonly connection: AdbServerConnection;
|
||||
|
||||
public constructor(connection: AdbServerConnection) {
|
||||
constructor(connection: AdbServerConnection) {
|
||||
this.connection = connection;
|
||||
}
|
||||
|
||||
public static readString(stream: ExactReadable): string;
|
||||
public static readString(stream: AsyncExactReadable): PromiseLike<string>;
|
||||
public static readString(
|
||||
static readString(stream: ExactReadable): string;
|
||||
static readString(stream: AsyncExactReadable): PromiseLike<string>;
|
||||
static readString(
|
||||
stream: ExactReadable | AsyncExactReadable,
|
||||
): string | PromiseLike<string> {
|
||||
return SyncPromise.try(() => stream.readExactly(4))
|
||||
|
@ -98,7 +98,7 @@ export class AdbServerClient {
|
|||
.valueOrPromise();
|
||||
}
|
||||
|
||||
public static async writeString(
|
||||
static async writeString(
|
||||
writer: WritableStreamDefaultWriter<Uint8Array>,
|
||||
value: string,
|
||||
): Promise<void> {
|
||||
|
@ -109,7 +109,7 @@ export class AdbServerClient {
|
|||
await writer.write(buffer);
|
||||
}
|
||||
|
||||
public static async readOkay(
|
||||
static async readOkay(
|
||||
stream: ExactReadable | AsyncExactReadable,
|
||||
): Promise<void> {
|
||||
const response = decodeUtf8(await stream.readExactly(4));
|
||||
|
@ -125,7 +125,7 @@ export class AdbServerClient {
|
|||
throw new Error(`Unexpected response: ${response}`);
|
||||
}
|
||||
|
||||
public async connect(
|
||||
async connect(
|
||||
request: string,
|
||||
options?: AdbServerConnectionOptions,
|
||||
): Promise<ReadableWritablePair<Uint8Array, Uint8Array>> {
|
||||
|
@ -156,7 +156,7 @@ export class AdbServerClient {
|
|||
}
|
||||
}
|
||||
|
||||
public async getVersion(): Promise<number> {
|
||||
async getVersion(): Promise<number> {
|
||||
const connection = await this.connect("host:version");
|
||||
const readable = new BufferedReadableStream(connection.readable);
|
||||
try {
|
||||
|
@ -169,7 +169,7 @@ export class AdbServerClient {
|
|||
}
|
||||
}
|
||||
|
||||
public async validateVersion() {
|
||||
async validateVersion() {
|
||||
const version = await this.getVersion();
|
||||
if (version !== AdbServerClient.VERSION) {
|
||||
throw new Error(
|
||||
|
@ -178,13 +178,13 @@ export class AdbServerClient {
|
|||
}
|
||||
}
|
||||
|
||||
public async killServer(): Promise<void> {
|
||||
async killServer(): Promise<void> {
|
||||
const connection = await this.connect("host:kill");
|
||||
connection.writable.close().catch(NOOP);
|
||||
connection.readable.cancel().catch(NOOP);
|
||||
}
|
||||
|
||||
public async getServerFeatures(): Promise<AdbFeature[]> {
|
||||
async getServerFeatures(): Promise<AdbFeature[]> {
|
||||
const connection = await this.connect("host:host-features");
|
||||
const readable = new BufferedReadableStream(connection.readable);
|
||||
try {
|
||||
|
@ -196,7 +196,7 @@ export class AdbServerClient {
|
|||
}
|
||||
}
|
||||
|
||||
public async getDevices(): Promise<AdbServerDevice[]> {
|
||||
async getDevices(): Promise<AdbServerDevice[]> {
|
||||
const connection = await this.connect("host:devices-l");
|
||||
const readable = new BufferedReadableStream(connection.readable);
|
||||
try {
|
||||
|
@ -253,10 +253,7 @@ export class AdbServerClient {
|
|||
}
|
||||
}
|
||||
|
||||
public formatDeviceService(
|
||||
device: AdbServerDeviceSelector,
|
||||
command: string,
|
||||
) {
|
||||
formatDeviceService(device: AdbServerDeviceSelector, command: string) {
|
||||
if (!device) {
|
||||
return `host:${command}`;
|
||||
}
|
||||
|
@ -282,7 +279,7 @@ export class AdbServerClient {
|
|||
* @param device The device selector
|
||||
* @returns The transport ID of the selected device, and the features supported by the device.
|
||||
*/
|
||||
public async getDeviceFeatures(
|
||||
async getDeviceFeatures(
|
||||
device: AdbServerDeviceSelector,
|
||||
): Promise<{ transportId: bigint; features: AdbFeature[] }> {
|
||||
// Usually the client sends a device command using `connectDevice`,
|
||||
|
@ -309,7 +306,7 @@ export class AdbServerClient {
|
|||
* @param service The service to forward
|
||||
* @returns An `AdbServerSocket` that can be used to communicate with the service
|
||||
*/
|
||||
public async connectDevice(
|
||||
async connectDevice(
|
||||
device: AdbServerDeviceSelector,
|
||||
service: string,
|
||||
): Promise<AdbServerSocket> {
|
||||
|
@ -386,7 +383,7 @@ export class AdbServerClient {
|
|||
* @param options The options
|
||||
* @returns A promise that resolves when the condition is met.
|
||||
*/
|
||||
public async waitFor(
|
||||
async waitFor(
|
||||
device: AdbServerDeviceSelector,
|
||||
state: "device" | "disconnect",
|
||||
options?: AdbServerConnectionOptions,
|
||||
|
@ -418,7 +415,7 @@ export class AdbServerClient {
|
|||
await this.connect(service, options);
|
||||
}
|
||||
|
||||
public async createTransport(
|
||||
async createTransport(
|
||||
device: AdbServerDeviceSelector,
|
||||
): Promise<AdbServerTransport> {
|
||||
const { transportId, features } = await this.getDeviceFeatures(device);
|
||||
|
|
|
@ -14,19 +14,19 @@ import type { AdbServerClient } from "./client.js";
|
|||
export class AdbServerTransport implements AdbTransport {
|
||||
#client: AdbServerClient;
|
||||
|
||||
public readonly serial: string;
|
||||
readonly serial: string;
|
||||
|
||||
public readonly transportId: bigint;
|
||||
readonly transportId: bigint;
|
||||
|
||||
public readonly maxPayloadSize: number = 1 * 1024 * 1024;
|
||||
readonly maxPayloadSize: number = 1 * 1024 * 1024;
|
||||
|
||||
public readonly banner: AdbBanner;
|
||||
readonly banner: AdbBanner;
|
||||
|
||||
#closed = new PromiseResolver<void>();
|
||||
#waitAbortController = new AbortController();
|
||||
public readonly disconnected: Promise<void>;
|
||||
readonly disconnected: Promise<void>;
|
||||
|
||||
public constructor(
|
||||
constructor(
|
||||
client: AdbServerClient,
|
||||
serial: string,
|
||||
banner: AdbBanner,
|
||||
|
@ -46,7 +46,7 @@ export class AdbServerTransport implements AdbTransport {
|
|||
]);
|
||||
}
|
||||
|
||||
public async connect(service: string): Promise<AdbSocket> {
|
||||
async connect(service: string): Promise<AdbSocket> {
|
||||
return await this.#client.connectDevice(
|
||||
{
|
||||
transportId: this.transportId,
|
||||
|
@ -55,18 +55,18 @@ export class AdbServerTransport implements AdbTransport {
|
|||
);
|
||||
}
|
||||
|
||||
public async addReverseTunnel(
|
||||
async addReverseTunnel(
|
||||
handler: AdbIncomingSocketHandler,
|
||||
address?: string,
|
||||
): Promise<string> {
|
||||
return await this.#client.connection.addReverseTunnel(handler, address);
|
||||
}
|
||||
|
||||
public async removeReverseTunnel(address: string): Promise<void> {
|
||||
async removeReverseTunnel(address: string): Promise<void> {
|
||||
await this.#client.connection.removeReverseTunnel(address);
|
||||
}
|
||||
|
||||
public async clearReverseTunnels(): Promise<void> {
|
||||
async clearReverseTunnels(): Promise<void> {
|
||||
await this.#client.connection.clearReverseTunnels();
|
||||
}
|
||||
|
||||
|
|
|
@ -5,11 +5,11 @@ export class AutoResetEvent implements Disposable {
|
|||
#set: boolean;
|
||||
readonly #queue: PromiseResolver<void>[] = [];
|
||||
|
||||
public constructor(initialSet = false) {
|
||||
constructor(initialSet = false) {
|
||||
this.#set = initialSet;
|
||||
}
|
||||
|
||||
public wait(): Promise<void> {
|
||||
wait(): Promise<void> {
|
||||
if (!this.#set) {
|
||||
this.#set = true;
|
||||
|
||||
|
@ -23,7 +23,7 @@ export class AutoResetEvent implements Disposable {
|
|||
return resolver.promise;
|
||||
}
|
||||
|
||||
public notifyOne() {
|
||||
notifyOne() {
|
||||
if (this.#queue.length !== 0) {
|
||||
this.#queue.pop()!.resolve();
|
||||
} else {
|
||||
|
@ -31,7 +31,7 @@ export class AutoResetEvent implements Disposable {
|
|||
}
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
dispose() {
|
||||
for (const item of this.#queue) {
|
||||
item.reject(new Error("The AutoResetEvent has been disposed"));
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ export class ConditionalVariable implements Disposable {
|
|||
#locked = false;
|
||||
readonly #queue: WaitEntry[] = [];
|
||||
|
||||
public wait(condition: () => boolean): Promise<void> {
|
||||
wait(condition: () => boolean): Promise<void> {
|
||||
if (!this.#locked) {
|
||||
this.#locked = true;
|
||||
if (this.#queue.length === 0 && condition()) {
|
||||
|
@ -23,7 +23,7 @@ export class ConditionalVariable implements Disposable {
|
|||
return resolver.promise;
|
||||
}
|
||||
|
||||
public notifyOne() {
|
||||
notifyOne() {
|
||||
const entry = this.#queue.shift();
|
||||
if (entry) {
|
||||
if (entry.condition()) {
|
||||
|
@ -34,7 +34,7 @@ export class ConditionalVariable implements Disposable {
|
|||
}
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
dispose(): void {
|
||||
for (const item of this.#queue) {
|
||||
item.resolver.reject(
|
||||
new Error("The ConditionalVariable has been disposed"),
|
||||
|
|
|
@ -21,7 +21,7 @@ export class AdbBackup extends AdbCommandBase {
|
|||
/**
|
||||
* User must confirm backup on device within 60 seconds.
|
||||
*/
|
||||
public async backup(
|
||||
async backup(
|
||||
options: AdbBackupOptions,
|
||||
): Promise<ReadableStream<Uint8Array>> {
|
||||
const args = ["bu", "backup"];
|
||||
|
@ -62,7 +62,7 @@ export class AdbBackup extends AdbCommandBase {
|
|||
* User must enter the password (if any) and
|
||||
* confirm restore on device within 60 seconds.
|
||||
*/
|
||||
public async restore(options: AdbRestoreOptions): Promise<void> {
|
||||
async restore(options: AdbRestoreOptions): Promise<void> {
|
||||
const args = ["bu", "restore"];
|
||||
if (options.user !== undefined) {
|
||||
args.push("--user", options.user.toString());
|
||||
|
|
|
@ -20,22 +20,22 @@ export interface BugReportZVersion {
|
|||
}
|
||||
|
||||
export class BugReportZ extends AdbCommandBase {
|
||||
public static VERSION_REGEX = /(\d+)\.(\d+)/;
|
||||
static VERSION_REGEX = /(\d+)\.(\d+)/;
|
||||
|
||||
public static BEGIN_REGEX = /BEGIN:(.*)/;
|
||||
static BEGIN_REGEX = /BEGIN:(.*)/;
|
||||
|
||||
public static PROGRESS_REGEX = /PROGRESS:(.*)\/(.*)/;
|
||||
static PROGRESS_REGEX = /PROGRESS:(.*)\/(.*)/;
|
||||
|
||||
public static OK_REGEX = /OK:(.*)/;
|
||||
static OK_REGEX = /OK:(.*)/;
|
||||
|
||||
public static FAIL_REGEX = /FAIL:(.*)/;
|
||||
static FAIL_REGEX = /FAIL:(.*)/;
|
||||
|
||||
/**
|
||||
* Retrieve the version of bugreportz.
|
||||
*
|
||||
* @returns a `BugReportVersion` object, or `undefined` if `bugreportz` is not available.
|
||||
*/
|
||||
public async version(): Promise<BugReportZVersion | undefined> {
|
||||
async version(): Promise<BugReportZVersion | undefined> {
|
||||
// bugreportz requires shell protocol
|
||||
if (!AdbSubprocessShellProtocol.isSupported(this.adb)) {
|
||||
return undefined;
|
||||
|
@ -65,7 +65,7 @@ export class BugReportZ extends AdbCommandBase {
|
|||
};
|
||||
}
|
||||
|
||||
public supportProgress(major: number, minor: number): boolean {
|
||||
supportProgress(major: number, minor: number): boolean {
|
||||
return major > 1 || minor >= 1;
|
||||
}
|
||||
|
||||
|
@ -78,7 +78,7 @@ export class BugReportZ extends AdbCommandBase {
|
|||
* @param onProgress Progress callback. Only specify this if `supportsProgress` is `true`.
|
||||
* @returns The path of the bugreport file.
|
||||
*/
|
||||
public async generate(
|
||||
async generate(
|
||||
onProgress?: (progress: string, total: string) => void,
|
||||
): Promise<string> {
|
||||
const process = await this.adb.subprocess.spawn([
|
||||
|
@ -133,11 +133,11 @@ export class BugReportZ extends AdbCommandBase {
|
|||
return filename;
|
||||
}
|
||||
|
||||
public supportStream(major: number, minor: number): boolean {
|
||||
supportStream(major: number, minor: number): boolean {
|
||||
return major > 1 || minor >= 2;
|
||||
}
|
||||
|
||||
public stream(): ReadableStream<Uint8Array> {
|
||||
stream(): ReadableStream<Uint8Array> {
|
||||
return new PushReadableStream(async (controller) => {
|
||||
const process = await this.adb.subprocess.spawn([
|
||||
"bugreportz",
|
||||
|
@ -173,7 +173,7 @@ export class BugReportZ extends AdbCommandBase {
|
|||
|
||||
// https://cs.android.com/android/platform/superproject/+/master:frameworks/native/cmds/bugreport/bugreport.cpp;drc=9b73bf07d73dbab5b792632e1e233edbad77f5fd;bpv=0;bpt=0
|
||||
export class BugReport extends AdbCommandBase {
|
||||
public generate(): ReadableStream<Uint8Array> {
|
||||
generate(): ReadableStream<Uint8Array> {
|
||||
return new WrapReadableStream(async () => {
|
||||
const process = await this.adb.subprocess.spawn(["bugreport"]);
|
||||
return process.stdout;
|
||||
|
|
|
@ -14,26 +14,26 @@ import { ConcatStringStream, DecodeUtf8Stream } from "@yume-chan/stream-extra";
|
|||
|
||||
export class Cmd extends AdbCommandBase {
|
||||
#supportsShellV2: boolean;
|
||||
public get supportsShellV2() {
|
||||
get supportsShellV2() {
|
||||
return this.#supportsShellV2;
|
||||
}
|
||||
|
||||
#supportsCmd: boolean;
|
||||
public get supportsCmd() {
|
||||
get supportsCmd() {
|
||||
return this.#supportsCmd;
|
||||
}
|
||||
|
||||
#supportsAbb: boolean;
|
||||
public get supportsAbb() {
|
||||
get supportsAbb() {
|
||||
return this.#supportsAbb;
|
||||
}
|
||||
|
||||
#supportsAbbExec: boolean;
|
||||
public get supportsAbbExec() {
|
||||
get supportsAbbExec() {
|
||||
return this.#supportsAbbExec;
|
||||
}
|
||||
|
||||
public constructor(adb: Adb) {
|
||||
constructor(adb: Adb) {
|
||||
super(adb);
|
||||
this.#supportsShellV2 = adb.supportsFeature(AdbFeature.ShellV2);
|
||||
this.#supportsCmd = adb.supportsFeature(AdbFeature.Cmd);
|
||||
|
@ -41,7 +41,7 @@ export class Cmd extends AdbCommandBase {
|
|||
this.#supportsAbbExec = adb.supportsFeature(AdbFeature.AbbExec);
|
||||
}
|
||||
|
||||
public async spawn(
|
||||
async spawn(
|
||||
shellProtocol: boolean,
|
||||
command: string,
|
||||
...args: string[]
|
||||
|
@ -76,7 +76,7 @@ export class Cmd extends AdbCommandBase {
|
|||
throw new Error("Not supported");
|
||||
}
|
||||
|
||||
public async spawnAndWait(
|
||||
async spawnAndWait(
|
||||
command: string,
|
||||
...args: string[]
|
||||
): Promise<AdbSubprocessWaitResult> {
|
||||
|
|
|
@ -52,55 +52,63 @@ export const DemoModeStatusBarModes = [
|
|||
export type DemoModeStatusBarMode = (typeof DemoModeStatusBarModes)[number];
|
||||
|
||||
export class DemoMode extends AdbCommandBase {
|
||||
private settings: Settings;
|
||||
#settings: Settings;
|
||||
|
||||
constructor(adb: Adb) {
|
||||
super(adb);
|
||||
this.settings = new Settings(adb);
|
||||
this.#settings = new Settings(adb);
|
||||
}
|
||||
|
||||
public static readonly AllowedSettingKey = "sysui_demo_allowed";
|
||||
static readonly ALLOWED_SETTING_KEY = "sysui_demo_allowed";
|
||||
|
||||
// Demo Mode actually doesn't have a setting indicates its enablement
|
||||
// However Developer Mode menu uses this key
|
||||
// So we can only try our best to guess if it's enabled
|
||||
public static readonly EnabledSettingKey = "sysui_tuner_demo_on";
|
||||
static readonly ENABLED_SETTING_KEY = "sysui_tuner_demo_on";
|
||||
|
||||
public async getAllowed(): Promise<boolean> {
|
||||
const output = await this.settings.get(
|
||||
async getAllowed(): Promise<boolean> {
|
||||
const output = await this.#settings.get(
|
||||
"global",
|
||||
DemoMode.AllowedSettingKey,
|
||||
DemoMode.ALLOWED_SETTING_KEY,
|
||||
);
|
||||
return output === "1";
|
||||
}
|
||||
|
||||
public async setAllowed(value: boolean): Promise<void> {
|
||||
async setAllowed(value: boolean): Promise<void> {
|
||||
if (value) {
|
||||
await this.settings.put("global", DemoMode.AllowedSettingKey, "1");
|
||||
await this.#settings.put(
|
||||
"global",
|
||||
DemoMode.ALLOWED_SETTING_KEY,
|
||||
"1",
|
||||
);
|
||||
} else {
|
||||
await this.setEnabled(false);
|
||||
await this.settings.delete("global", DemoMode.AllowedSettingKey);
|
||||
await this.#settings.delete("global", DemoMode.ALLOWED_SETTING_KEY);
|
||||
}
|
||||
}
|
||||
|
||||
public async getEnabled(): Promise<boolean> {
|
||||
const result = await this.settings.get(
|
||||
async getEnabled(): Promise<boolean> {
|
||||
const result = await this.#settings.get(
|
||||
"global",
|
||||
DemoMode.EnabledSettingKey,
|
||||
DemoMode.ENABLED_SETTING_KEY,
|
||||
);
|
||||
return result === "1";
|
||||
}
|
||||
|
||||
public async setEnabled(value: boolean): Promise<void> {
|
||||
async setEnabled(value: boolean): Promise<void> {
|
||||
if (value) {
|
||||
await this.settings.put("global", DemoMode.EnabledSettingKey, "1");
|
||||
await this.#settings.put(
|
||||
"global",
|
||||
DemoMode.ENABLED_SETTING_KEY,
|
||||
"1",
|
||||
);
|
||||
} else {
|
||||
await this.settings.delete("global", DemoMode.EnabledSettingKey);
|
||||
await this.#settings.delete("global", DemoMode.ENABLED_SETTING_KEY);
|
||||
await this.broadcast("exit");
|
||||
}
|
||||
}
|
||||
|
||||
public async broadcast(
|
||||
async broadcast(
|
||||
command: string,
|
||||
extra?: Record<string, string>,
|
||||
): Promise<void> {
|
||||
|
@ -122,31 +130,27 @@ export class DemoMode extends AdbCommandBase {
|
|||
]);
|
||||
}
|
||||
|
||||
public async setBatteryLevel(level: number): Promise<void> {
|
||||
async setBatteryLevel(level: number): Promise<void> {
|
||||
await this.broadcast("battery", { level: level.toString() });
|
||||
}
|
||||
|
||||
public async setBatteryCharging(value: boolean): Promise<void> {
|
||||
async setBatteryCharging(value: boolean): Promise<void> {
|
||||
await this.broadcast("battery", { plugged: value.toString() });
|
||||
}
|
||||
|
||||
public async setPowerSaveMode(value: boolean): Promise<void> {
|
||||
async setPowerSaveMode(value: boolean): Promise<void> {
|
||||
await this.broadcast("battery", { powersave: value.toString() });
|
||||
}
|
||||
|
||||
public async setAirplaneMode(show: boolean): Promise<void> {
|
||||
async setAirplaneMode(show: boolean): Promise<void> {
|
||||
await this.broadcast("network", { airplane: show ? "show" : "hide" });
|
||||
}
|
||||
|
||||
public async setWifiSignalStrength(
|
||||
value: DemoModeSignalStrength,
|
||||
): Promise<void> {
|
||||
async setWifiSignalStrength(value: DemoModeSignalStrength): Promise<void> {
|
||||
await this.broadcast("network", { wifi: "show", level: value });
|
||||
}
|
||||
|
||||
public async setMobileDataType(
|
||||
value: DemoModeMobileDataType,
|
||||
): Promise<void> {
|
||||
async setMobileDataType(value: DemoModeMobileDataType): Promise<void> {
|
||||
for (let i = 0; i < 2; i += 1) {
|
||||
await this.broadcast("network", {
|
||||
mobile: "show",
|
||||
|
@ -164,60 +168,60 @@ export class DemoMode extends AdbCommandBase {
|
|||
}
|
||||
}
|
||||
|
||||
public async setMobileSignalStrength(
|
||||
async setMobileSignalStrength(
|
||||
value: DemoModeSignalStrength,
|
||||
): Promise<void> {
|
||||
await this.broadcast("network", { mobile: "show", level: value });
|
||||
}
|
||||
|
||||
public async setNoSimCardIcon(show: boolean): Promise<void> {
|
||||
async setNoSimCardIcon(show: boolean): Promise<void> {
|
||||
await this.broadcast("network", { nosim: show ? "show" : "hide" });
|
||||
}
|
||||
|
||||
public async setStatusBarMode(mode: DemoModeStatusBarMode): Promise<void> {
|
||||
async setStatusBarMode(mode: DemoModeStatusBarMode): Promise<void> {
|
||||
await this.broadcast("bars", { mode });
|
||||
}
|
||||
|
||||
public async setVibrateModeEnabled(value: boolean): Promise<void> {
|
||||
async setVibrateModeEnabled(value: boolean): Promise<void> {
|
||||
// https://cs.android.com/android/platform/superproject/+/master:frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java;l=103
|
||||
await this.broadcast("status", { volume: value ? "vibrate" : "hide" });
|
||||
}
|
||||
|
||||
public async setBluetoothConnected(value: boolean): Promise<void> {
|
||||
async setBluetoothConnected(value: boolean): Promise<void> {
|
||||
// https://cs.android.com/android/platform/superproject/+/master:frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java;l=114
|
||||
await this.broadcast("status", {
|
||||
bluetooth: value ? "connected" : "hide",
|
||||
});
|
||||
}
|
||||
|
||||
public async setLocatingIcon(show: boolean): Promise<void> {
|
||||
async setLocatingIcon(show: boolean): Promise<void> {
|
||||
await this.broadcast("status", { location: show ? "show" : "hide" });
|
||||
}
|
||||
|
||||
public async setAlarmIcon(show: boolean): Promise<void> {
|
||||
async setAlarmIcon(show: boolean): Promise<void> {
|
||||
await this.broadcast("status", { alarm: show ? "show" : "hide" });
|
||||
}
|
||||
|
||||
public async setSyncingIcon(show: boolean): Promise<void> {
|
||||
async setSyncingIcon(show: boolean): Promise<void> {
|
||||
await this.broadcast("status", { sync: show ? "show" : "hide" });
|
||||
}
|
||||
|
||||
public async setMuteIcon(show: boolean): Promise<void> {
|
||||
async setMuteIcon(show: boolean): Promise<void> {
|
||||
await this.broadcast("status", { mute: show ? "show" : "hide" });
|
||||
}
|
||||
|
||||
public async setSpeakerPhoneIcon(show: boolean): Promise<void> {
|
||||
async setSpeakerPhoneIcon(show: boolean): Promise<void> {
|
||||
await this.broadcast("status", {
|
||||
speakerphone: show ? "show" : "hide",
|
||||
});
|
||||
}
|
||||
|
||||
public async setNotificationsVisibility(show: boolean): Promise<void> {
|
||||
async setNotificationsVisibility(show: boolean): Promise<void> {
|
||||
// https://cs.android.com/android/platform/superproject/+/master:frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java;l=3131
|
||||
await this.broadcast("notifications", { visible: show.toString() });
|
||||
}
|
||||
|
||||
public async setTime(hour: number, minute: number): Promise<void> {
|
||||
async setTime(hour: number, minute: number): Promise<void> {
|
||||
await this.broadcast("clock", {
|
||||
// cspell: disable-next-line
|
||||
hhmm:
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { AdbCommandBase } from "@yume-chan/adb";
|
||||
|
||||
export class DumpSys extends AdbCommandBase {
|
||||
public async diskStats() {
|
||||
async diskStats() {
|
||||
const output = await this.adb.subprocess.spawnAndWaitLegacy([
|
||||
"dumpsys",
|
||||
"diskstats",
|
||||
|
@ -34,7 +34,7 @@ export class DumpSys extends AdbCommandBase {
|
|||
};
|
||||
}
|
||||
|
||||
public async battery() {
|
||||
async battery() {
|
||||
const output = await this.adb.subprocess.spawnAndWaitLegacy([
|
||||
"dumpsys",
|
||||
"battery",
|
||||
|
|
|
@ -382,35 +382,35 @@ export interface LogSize {
|
|||
}
|
||||
|
||||
export class Logcat extends AdbCommandBase {
|
||||
public static logIdToName(id: LogId): string {
|
||||
static logIdToName(id: LogId): string {
|
||||
return LogId[id]!;
|
||||
}
|
||||
|
||||
public static logNameToId(name: string): LogId {
|
||||
static logNameToId(name: string): LogId {
|
||||
const key = name[0]!.toUpperCase() + name.substring(1);
|
||||
return LogId[key as keyof typeof LogId];
|
||||
}
|
||||
|
||||
public static joinLogId(ids: LogId[]): string {
|
||||
static joinLogId(ids: LogId[]): string {
|
||||
return ids.map((id) => Logcat.logIdToName(id)).join(",");
|
||||
}
|
||||
|
||||
public static parseSize(value: number, multiplier: string): number {
|
||||
static parseSize(value: number, multiplier: string): number {
|
||||
const MULTIPLIERS = ["", "Ki", "Mi", "Gi"];
|
||||
return value * 1024 ** (MULTIPLIERS.indexOf(multiplier) || 0);
|
||||
}
|
||||
|
||||
// TODO: logcat: Support output format before Android 10
|
||||
// ref https://android-review.googlesource.com/c/platform/system/core/+/748128
|
||||
public static readonly LOG_SIZE_REGEX_10 =
|
||||
static readonly LOG_SIZE_REGEX_10 =
|
||||
/(.*): ring buffer is (.*) (.*)B \((.*) (.*)B consumed\), max entry is (.*) B, max payload is (.*) B/;
|
||||
|
||||
// Android 11 added `readable` part
|
||||
// ref https://android-review.googlesource.com/c/platform/system/core/+/1390940
|
||||
public static readonly LOG_SIZE_REGEX_11 =
|
||||
static readonly LOG_SIZE_REGEX_11 =
|
||||
/(.*): ring buffer is (.*) (.*)B \((.*) (.*)B consumed, (.*) (.*)B readable\), max entry is (.*) B, max payload is (.*) B/;
|
||||
|
||||
public async getLogSize(ids?: LogId[]): Promise<LogSize[]> {
|
||||
async getLogSize(ids?: LogId[]): Promise<LogSize[]> {
|
||||
const { stdout } = await this.adb.subprocess.spawn([
|
||||
"logcat",
|
||||
"-g",
|
||||
|
@ -469,7 +469,7 @@ export class Logcat extends AdbCommandBase {
|
|||
return result;
|
||||
}
|
||||
|
||||
public async clear(ids?: LogId[]) {
|
||||
async clear(ids?: LogId[]) {
|
||||
await this.adb.subprocess.spawnAndWait([
|
||||
"logcat",
|
||||
"-c",
|
||||
|
@ -477,7 +477,7 @@ export class Logcat extends AdbCommandBase {
|
|||
]);
|
||||
}
|
||||
|
||||
public binary(options?: LogcatOptions): ReadableStream<AndroidLogEntry> {
|
||||
binary(options?: LogcatOptions): ReadableStream<AndroidLogEntry> {
|
||||
return new WrapReadableStream(async () => {
|
||||
// TODO: make `spawn` return synchronously with streams pending
|
||||
// so it's easier to chain them.
|
||||
|
|
|
@ -3,12 +3,12 @@ import { describe, expect, it } from "@jest/globals";
|
|||
import { OverlayDisplay } from "./overlay-display.js";
|
||||
|
||||
describe("OverlayDisplay", () => {
|
||||
describe("OverlayDisplayDevicesFormat", () => {
|
||||
describe("SETTING_FORMAT", () => {
|
||||
// values are from https://cs.android.com/android/platform/superproject/+/master:frameworks/base/packages/SettingsLib/res/values/arrays.xml;l=468;drc=60c1d392225bc6e1601693c7d5cfdf1d7f510015
|
||||
|
||||
it("should parse 0 device", () => {
|
||||
expect(
|
||||
OverlayDisplay.OverlayDisplayDevicesFormat.parse({
|
||||
OverlayDisplay.SETTING_FORMAT.parse({
|
||||
value: "",
|
||||
position: 0,
|
||||
}),
|
||||
|
@ -17,7 +17,7 @@ describe("OverlayDisplay", () => {
|
|||
|
||||
it("should parse 1 mode", () => {
|
||||
expect(
|
||||
OverlayDisplay.OverlayDisplayDevicesFormat.parse({
|
||||
OverlayDisplay.SETTING_FORMAT.parse({
|
||||
value: "720x480/142",
|
||||
position: 0,
|
||||
}),
|
||||
|
@ -37,7 +37,7 @@ describe("OverlayDisplay", () => {
|
|||
|
||||
it("should parse 2 modes", () => {
|
||||
expect(
|
||||
OverlayDisplay.OverlayDisplayDevicesFormat.parse({
|
||||
OverlayDisplay.SETTING_FORMAT.parse({
|
||||
value: "1920x1080/320|3840x2160/640",
|
||||
position: 0,
|
||||
}),
|
||||
|
@ -62,7 +62,7 @@ describe("OverlayDisplay", () => {
|
|||
|
||||
it("should parse 2 device", () => {
|
||||
expect(
|
||||
OverlayDisplay.OverlayDisplayDevicesFormat.parse({
|
||||
OverlayDisplay.SETTING_FORMAT.parse({
|
||||
value: "1280x720/213;1920x1080/320",
|
||||
position: 0,
|
||||
}),
|
||||
|
@ -92,7 +92,7 @@ describe("OverlayDisplay", () => {
|
|||
|
||||
it("should parse flags", () => {
|
||||
expect(
|
||||
OverlayDisplay.OverlayDisplayDevicesFormat.parse({
|
||||
OverlayDisplay.SETTING_FORMAT.parse({
|
||||
value: "1920x1080/320|3840x2160/640,secure",
|
||||
position: 0,
|
||||
}),
|
||||
|
|
|
@ -18,12 +18,11 @@ export interface OverlayDisplayDevice {
|
|||
}
|
||||
|
||||
export class OverlayDisplay extends AdbCommandBase {
|
||||
private settings: Settings;
|
||||
#settings: Settings;
|
||||
|
||||
public static readonly OVERLAY_DISPLAY_DEVICES_KEY =
|
||||
"overlay_display_devices";
|
||||
static readonly SETTING_KEY = "overlay_display_devices";
|
||||
|
||||
public static readonly OverlayDisplayDevicesFormat = p.separated(
|
||||
static readonly SETTING_FORMAT = p.separated(
|
||||
";",
|
||||
p.sequence(
|
||||
{
|
||||
|
@ -62,14 +61,14 @@ export class OverlayDisplay extends AdbCommandBase {
|
|||
|
||||
constructor(adb: Adb) {
|
||||
super(adb);
|
||||
this.settings = new Settings(adb);
|
||||
this.#settings = new Settings(adb);
|
||||
}
|
||||
|
||||
public async get() {
|
||||
return OverlayDisplay.OverlayDisplayDevicesFormat.parse({
|
||||
value: await this.settings.get(
|
||||
async get() {
|
||||
return OverlayDisplay.SETTING_FORMAT.parse({
|
||||
value: await this.#settings.get(
|
||||
"global",
|
||||
OverlayDisplay.OVERLAY_DISPLAY_DEVICES_KEY,
|
||||
OverlayDisplay.SETTING_KEY,
|
||||
),
|
||||
position: 0,
|
||||
}).map((device) => ({
|
||||
|
@ -82,11 +81,11 @@ export class OverlayDisplay extends AdbCommandBase {
|
|||
}));
|
||||
}
|
||||
|
||||
public async set(devices: OverlayDisplayDevice[]) {
|
||||
await this.settings.put(
|
||||
async set(devices: OverlayDisplayDevice[]) {
|
||||
await this.#settings.put(
|
||||
"global",
|
||||
OverlayDisplay.OVERLAY_DISPLAY_DEVICES_KEY,
|
||||
OverlayDisplay.OverlayDisplayDevicesFormat.stringify(
|
||||
OverlayDisplay.SETTING_KEY,
|
||||
OverlayDisplay.SETTING_FORMAT.stringify(
|
||||
devices.map((device) => {
|
||||
const flags: (
|
||||
| "secure"
|
||||
|
|
|
@ -219,14 +219,14 @@ export interface PackageManagerListPackagesResult {
|
|||
}
|
||||
|
||||
export class PackageManager extends AdbCommandBase {
|
||||
private _cmd: Cmd;
|
||||
#cmd: Cmd;
|
||||
|
||||
public constructor(adb: Adb) {
|
||||
constructor(adb: Adb) {
|
||||
super(adb);
|
||||
this._cmd = new Cmd(adb);
|
||||
this.#cmd = new Cmd(adb);
|
||||
}
|
||||
|
||||
private buildArguments<T>(
|
||||
#buildArguments<T>(
|
||||
commands: string[],
|
||||
options: Partial<T> | undefined,
|
||||
map: Record<keyof T, string>,
|
||||
|
@ -253,27 +253,27 @@ export class PackageManager extends AdbCommandBase {
|
|||
return args;
|
||||
}
|
||||
|
||||
private buildInstallArguments(
|
||||
#buildInstallArguments(
|
||||
options: Partial<PackageManagerInstallOptions> | undefined,
|
||||
): string[] {
|
||||
return this.buildArguments(
|
||||
return this.#buildArguments(
|
||||
["install"],
|
||||
options,
|
||||
PACKAGE_MANAGER_INSTALL_OPTIONS_MAP,
|
||||
);
|
||||
}
|
||||
|
||||
public async install(
|
||||
async install(
|
||||
apks: string[],
|
||||
options?: Partial<PackageManagerInstallOptions>,
|
||||
): Promise<string> {
|
||||
const args = this.buildInstallArguments(options);
|
||||
const args = this.#buildInstallArguments(options);
|
||||
// WIP: old version of pm doesn't support multiple apks
|
||||
args.push(...apks);
|
||||
return await this.adb.subprocess.spawnAndWaitLegacy(args);
|
||||
}
|
||||
|
||||
public async pushAndInstallStream(
|
||||
async pushAndInstallStream(
|
||||
stream: ReadableStream<Consumable<Uint8Array>>,
|
||||
options?: Partial<PackageManagerInstallOptions>,
|
||||
): Promise<ReadableStream<string>> {
|
||||
|
@ -295,7 +295,7 @@ export class PackageManager extends AdbCommandBase {
|
|||
// and `cmd package` launches faster than `pm`.
|
||||
// But `cmd package` can't read `/data/local/tmp` folder due to SELinux policy,
|
||||
// so installing a file must use `pm`.
|
||||
const args = this.buildInstallArguments(options);
|
||||
const args = this.#buildInstallArguments(options);
|
||||
args.push(filePath);
|
||||
const process = await AdbSubprocessNoneProtocol.raw(
|
||||
this.adb,
|
||||
|
@ -309,7 +309,7 @@ export class PackageManager extends AdbCommandBase {
|
|||
});
|
||||
}
|
||||
|
||||
public async installStream(
|
||||
async installStream(
|
||||
size: number,
|
||||
stream: ReadableStream<Consumable<Uint8Array>>,
|
||||
options?: Partial<PackageManagerInstallOptions>,
|
||||
|
@ -317,20 +317,20 @@ export class PackageManager extends AdbCommandBase {
|
|||
// Android 7 added both `cmd` command and streaming install support,
|
||||
// we can't detect whether `pm` supports streaming install,
|
||||
// so we detect `cmd` command support instead.
|
||||
if (!this._cmd.supportsCmd) {
|
||||
if (!this.#cmd.supportsCmd) {
|
||||
return this.pushAndInstallStream(stream, options);
|
||||
}
|
||||
|
||||
const args = this.buildInstallArguments(options);
|
||||
const args = this.#buildInstallArguments(options);
|
||||
// Remove `pm` from args, final command will starts with `cmd package install`
|
||||
args.shift();
|
||||
args.push("-S", size.toString());
|
||||
const process = await this._cmd.spawn(false, "package", ...args);
|
||||
const process = await this.#cmd.spawn(false, "package", ...args);
|
||||
await stream.pipeTo(process.stdin);
|
||||
return process.stdout.pipeThrough(new DecodeUtf8Stream());
|
||||
}
|
||||
|
||||
public static parsePackageListItem(
|
||||
static parsePackageListItem(
|
||||
line: string,
|
||||
): PackageManagerListPackagesResult {
|
||||
line = line.substring("package:".length);
|
||||
|
@ -381,10 +381,10 @@ export class PackageManager extends AdbCommandBase {
|
|||
};
|
||||
}
|
||||
|
||||
public async listPackages(
|
||||
async listPackages(
|
||||
options?: Partial<PackageManagerListPackagesOptions>,
|
||||
): Promise<PackageManagerListPackagesResult[]> {
|
||||
const args = this.buildArguments(
|
||||
const args = this.#buildArguments(
|
||||
["list", "packages"],
|
||||
options,
|
||||
PACKAGE_MANAGER_LIST_PACKAGES_OPTIONS_MAP,
|
||||
|
|
|
@ -24,12 +24,12 @@ export interface SettingsPutOptions extends SettingsOptions {
|
|||
export class Settings extends AdbCommandBase {
|
||||
#cmd: Cmd;
|
||||
|
||||
public constructor(adb: Adb) {
|
||||
constructor(adb: Adb) {
|
||||
super(adb);
|
||||
this.#cmd = new Cmd(adb);
|
||||
}
|
||||
|
||||
public async base(
|
||||
async base(
|
||||
verb: string,
|
||||
namespace: SettingsNamespace,
|
||||
options: SettingsOptions | undefined,
|
||||
|
@ -61,7 +61,7 @@ export class Settings extends AdbCommandBase {
|
|||
return output.stdout;
|
||||
}
|
||||
|
||||
public async get(
|
||||
async get(
|
||||
namespace: SettingsNamespace,
|
||||
key: string,
|
||||
options?: SettingsOptions,
|
||||
|
@ -71,7 +71,7 @@ export class Settings extends AdbCommandBase {
|
|||
return output.substring(0, output.length - 1);
|
||||
}
|
||||
|
||||
public async delete(
|
||||
async delete(
|
||||
namespace: SettingsNamespace,
|
||||
key: string,
|
||||
options?: SettingsOptions,
|
||||
|
@ -79,7 +79,7 @@ export class Settings extends AdbCommandBase {
|
|||
await this.base("delete", namespace, options, key);
|
||||
}
|
||||
|
||||
public async put(
|
||||
async put(
|
||||
namespace: SettingsNamespace,
|
||||
key: string,
|
||||
value: string,
|
||||
|
@ -95,18 +95,18 @@ export class Settings extends AdbCommandBase {
|
|||
await this.base("put", namespace, options, ...args);
|
||||
}
|
||||
|
||||
public reset(
|
||||
reset(
|
||||
namespace: SettingsNamespace,
|
||||
mode: SettingsResetMode,
|
||||
options?: SettingsOptions,
|
||||
): Promise<void>;
|
||||
public reset(
|
||||
reset(
|
||||
namespace: SettingsNamespace,
|
||||
packageName: string,
|
||||
tag?: string,
|
||||
options?: SettingsOptions,
|
||||
): Promise<void>;
|
||||
public async reset(
|
||||
async reset(
|
||||
namespace: SettingsNamespace,
|
||||
modeOrPackageName: string,
|
||||
tagOrOptions?: string | SettingsOptions,
|
||||
|
|
|
@ -6,7 +6,7 @@ export class ParseError extends Error {
|
|||
return this.#expected;
|
||||
}
|
||||
|
||||
public constructor(expected: string[]) {
|
||||
constructor(expected: string[]) {
|
||||
super(`Expected ${expected.join(", ")}`);
|
||||
this.#expected = expected;
|
||||
}
|
||||
|
@ -29,34 +29,20 @@ type UnionResult<T extends readonly Format<unknown>[]> = Exclude<
|
|||
undefined
|
||||
>;
|
||||
|
||||
type UnionToIntersection<T> = (
|
||||
T extends unknown ? (x: T) => void : never
|
||||
) extends (x: infer R) => void
|
||||
? R
|
||||
: never;
|
||||
|
||||
type SequenceResult<
|
||||
T extends readonly (
|
||||
| Format<unknown>
|
||||
| { name: string; format: Format<unknown> }
|
||||
)[],
|
||||
> = UnionToIntersection<
|
||||
{
|
||||
[K in keyof T]: T[K] extends {
|
||||
name: string;
|
||||
format: Format<unknown>;
|
||||
}
|
||||
? Record<
|
||||
T[K]["name"],
|
||||
T[K]["format"] extends Format<infer F>
|
||||
? Exclude<F, undefined>
|
||||
> = {
|
||||
[K in keyof T as K extends `${number}`
|
||||
? T[K] extends { name: infer N extends string }
|
||||
? N
|
||||
: never
|
||||
>
|
||||
: never]: T[K] extends { format: Format<infer F> } ? F : never;
|
||||
} extends infer R extends Record<string, unknown>
|
||||
? R
|
||||
: never;
|
||||
}[number]
|
||||
>;
|
||||
|
||||
type Evaluate<T> = T extends infer U ? { [K in keyof U]: U[K] } : never;
|
||||
|
||||
export const p = {
|
||||
literal: <T extends string>(value: T): Format<T> => ({
|
||||
|
@ -174,7 +160,7 @@ export const p = {
|
|||
)[],
|
||||
>(
|
||||
...args: T
|
||||
): Format<Evaluate<SequenceResult<T>>> => ({
|
||||
): Format<SequenceResult<T>> => ({
|
||||
parse(reader: Reader) {
|
||||
const result: Record<string, unknown> = {};
|
||||
for (const part of args) {
|
||||
|
@ -184,9 +170,9 @@ export const p = {
|
|||
void part.parse(reader);
|
||||
}
|
||||
}
|
||||
return result as Evaluate<SequenceResult<T>>;
|
||||
return result as SequenceResult<T>;
|
||||
},
|
||||
stringify: (value: Evaluate<SequenceResult<T>>) => {
|
||||
stringify: (value: SequenceResult<T>) => {
|
||||
let result = "";
|
||||
for (const part of args) {
|
||||
if ("name" in part) {
|
||||
|
|
|
@ -77,7 +77,7 @@ export class AoaHidDevice {
|
|||
* @param reportDescriptor The HID report descriptor.
|
||||
* @returns An instance of AoaHidDevice to send events.
|
||||
*/
|
||||
public static async register(
|
||||
static async register(
|
||||
device: USBDevice,
|
||||
accessoryId: number,
|
||||
reportDescriptor: Uint8Array
|
||||
|
@ -87,19 +87,19 @@ export class AoaHidDevice {
|
|||
return new AoaHidDevice(device, accessoryId);
|
||||
}
|
||||
|
||||
private _device: USBDevice;
|
||||
private _accessoryId: number;
|
||||
#device: USBDevice;
|
||||
#accessoryId: number;
|
||||
|
||||
private constructor(device: USBDevice, accessoryId: number) {
|
||||
this._device = device;
|
||||
this._accessoryId = accessoryId;
|
||||
constructor(device: USBDevice, accessoryId: number) {
|
||||
this.#device = device;
|
||||
this.#accessoryId = accessoryId;
|
||||
}
|
||||
|
||||
public async sendInputReport(event: Uint8Array) {
|
||||
await aoaHidSendInputReport(this._device, this._accessoryId, event);
|
||||
async sendInputReport(event: Uint8Array) {
|
||||
await aoaHidSendInputReport(this.#device, this.#accessoryId, event);
|
||||
}
|
||||
|
||||
public async unregister() {
|
||||
await aoaHidUnregister(this._device, this._accessoryId);
|
||||
async unregister() {
|
||||
await aoaHidUnregister(this.#device, this.#accessoryId);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -229,7 +229,7 @@ export class HidKeyboard {
|
|||
* It's compatible with the legacy boot protocol. (1 byte modifier, 1 byte reserved, 6 bytes key codes).
|
||||
* Technically it doesn't need to be compatible with the legacy boot protocol, but it's the most common implementation.
|
||||
*/
|
||||
public static readonly DESCRIPTOR = new Uint8Array(
|
||||
static readonly DESCRIPTOR = new Uint8Array(
|
||||
// prettier-ignore
|
||||
[
|
||||
0x05, 0x01, // Usage Page (Generic Desktop)
|
||||
|
@ -271,35 +271,35 @@ export class HidKeyboard {
|
|||
]
|
||||
);
|
||||
|
||||
private _modifiers = 0;
|
||||
private _keys: Set<HidKeyCode> = new Set();
|
||||
#modifiers = 0;
|
||||
#keys: Set<HidKeyCode> = new Set();
|
||||
|
||||
public down(key: HidKeyCode) {
|
||||
down(key: HidKeyCode) {
|
||||
if (key >= HidKeyCode.ControlLeft && key <= HidKeyCode.MetaRight) {
|
||||
this._modifiers |= 1 << (key - HidKeyCode.ControlLeft);
|
||||
this.#modifiers |= 1 << (key - HidKeyCode.ControlLeft);
|
||||
} else {
|
||||
this._keys.add(key);
|
||||
this.#keys.add(key);
|
||||
}
|
||||
}
|
||||
|
||||
public up(key: HidKeyCode) {
|
||||
up(key: HidKeyCode) {
|
||||
if (key >= HidKeyCode.ControlLeft && key <= HidKeyCode.MetaRight) {
|
||||
this._modifiers &= ~(1 << (key - HidKeyCode.ControlLeft));
|
||||
this.#modifiers &= ~(1 << (key - HidKeyCode.ControlLeft));
|
||||
} else {
|
||||
this._keys.delete(key);
|
||||
this.#keys.delete(key);
|
||||
}
|
||||
}
|
||||
|
||||
public reset() {
|
||||
this._modifiers = 0;
|
||||
this._keys.clear();
|
||||
reset() {
|
||||
this.#modifiers = 0;
|
||||
this.#keys.clear();
|
||||
}
|
||||
|
||||
public serializeInputReport() {
|
||||
serializeInputReport() {
|
||||
const buffer = new Uint8Array(8);
|
||||
buffer[0] = this._modifiers;
|
||||
buffer[0] = this.#modifiers;
|
||||
let i = 2;
|
||||
for (const key of this._keys) {
|
||||
for (const key of this.#keys) {
|
||||
buffer[i] = key;
|
||||
i += 1;
|
||||
if (i >= 8) {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
export class HidMouse {
|
||||
public static readonly descriptor = new Uint8Array(
|
||||
static readonly descriptor = new Uint8Array(
|
||||
// prettier-ignore
|
||||
[
|
||||
0x05, 0x01, // Usage Page (Generic Desktop)
|
||||
|
@ -42,7 +42,7 @@ export class HidMouse {
|
|||
]
|
||||
);
|
||||
|
||||
public static serializeInputReport(
|
||||
static serializeInputReport(
|
||||
movementX: number,
|
||||
movementY: number,
|
||||
buttons: number,
|
||||
|
|
|
@ -81,40 +81,40 @@ interface Finger {
|
|||
* A ten-point touch screen.
|
||||
*/
|
||||
export class HidTouchScreen {
|
||||
public static readonly FINGER_DESCRIPTOR = FINGER_DESCRIPTOR;
|
||||
static readonly FINGER_DESCRIPTOR = FINGER_DESCRIPTOR;
|
||||
|
||||
public static readonly DESCRIPTOR = DESCRIPTOR;
|
||||
static readonly DESCRIPTOR = DESCRIPTOR;
|
||||
|
||||
private fingers: Map<number, Finger> = new Map();
|
||||
#fingers: Map<number, Finger> = new Map();
|
||||
|
||||
public down(id: number, x: number, y: number) {
|
||||
if (this.fingers.size >= 10) {
|
||||
down(id: number, x: number, y: number) {
|
||||
if (this.#fingers.size >= 10) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.fingers.set(id, {
|
||||
this.#fingers.set(id, {
|
||||
x,
|
||||
y,
|
||||
});
|
||||
}
|
||||
|
||||
public move(id: number, x: number, y: number) {
|
||||
const finger = this.fingers.get(id);
|
||||
move(id: number, x: number, y: number) {
|
||||
const finger = this.#fingers.get(id);
|
||||
if (finger) {
|
||||
finger.x = x;
|
||||
finger.y = y;
|
||||
}
|
||||
}
|
||||
|
||||
public up(id: number) {
|
||||
this.fingers.delete(id);
|
||||
up(id: number) {
|
||||
this.#fingers.delete(id);
|
||||
}
|
||||
|
||||
public serializeInputReport(): Uint8Array {
|
||||
serializeInputReport(): Uint8Array {
|
||||
const report = new Uint8Array(1 + 6 * 10);
|
||||
report[0] = this.fingers.size;
|
||||
report[0] = this.#fingers.size;
|
||||
let offset = 1;
|
||||
for (const [id, finger] of this.fingers) {
|
||||
for (const [id, finger] of this.#fingers) {
|
||||
report[offset] = id;
|
||||
offset += 1;
|
||||
|
||||
|
|
|
@ -43,11 +43,11 @@ describe("BTree", () => {
|
|||
|
||||
function validateTree(tree: BTree) {
|
||||
if (tree.size === 0) {
|
||||
expect(tree["_root"].keyCount).toBe(0);
|
||||
expect(tree.root.keyCount).toBe(0);
|
||||
return;
|
||||
}
|
||||
|
||||
validateNode(tree["_root"], true);
|
||||
validateNode(tree.root, true);
|
||||
}
|
||||
|
||||
for (let order = 3; order < 10; order += 1) {
|
||||
|
|
|
@ -32,7 +32,7 @@ export class BTreeNode {
|
|||
height: number;
|
||||
children: BTreeNode[];
|
||||
|
||||
public constructor(
|
||||
constructor(
|
||||
order: number,
|
||||
keys: Int32Array,
|
||||
keyCount: number,
|
||||
|
@ -124,7 +124,7 @@ export class BTreeNode {
|
|||
};
|
||||
}
|
||||
|
||||
public search(value: number): number {
|
||||
search(value: number): number {
|
||||
let start = 0;
|
||||
let end = this.keyCount - 1;
|
||||
while (start <= end) {
|
||||
|
@ -140,7 +140,7 @@ export class BTreeNode {
|
|||
return ~start;
|
||||
}
|
||||
|
||||
public has(value: number): boolean {
|
||||
has(value: number): boolean {
|
||||
let index = this.search(value);
|
||||
|
||||
if (index >= 0) {
|
||||
|
@ -155,7 +155,7 @@ export class BTreeNode {
|
|||
return false;
|
||||
}
|
||||
|
||||
public add(value: number): BTreeInsertionResult | boolean {
|
||||
add(value: number): BTreeInsertionResult | boolean {
|
||||
let index = this.search(value);
|
||||
if (index >= 0) {
|
||||
return false;
|
||||
|
@ -188,7 +188,7 @@ export class BTreeNode {
|
|||
return true;
|
||||
}
|
||||
|
||||
public delete(value: number): boolean {
|
||||
delete(value: number): boolean {
|
||||
let index = this.search(value);
|
||||
if (index >= 0) {
|
||||
this.deleteAt(index);
|
||||
|
@ -209,7 +209,7 @@ export class BTreeNode {
|
|||
return deleted;
|
||||
}
|
||||
|
||||
public max(): number {
|
||||
max(): number {
|
||||
if (this.height === 0) {
|
||||
return this.keys[this.keyCount - 1]!;
|
||||
}
|
||||
|
@ -315,7 +315,7 @@ export class BTreeNode {
|
|||
this.balance(index);
|
||||
}
|
||||
|
||||
public *[Symbol.iterator](): Generator<number, void, void> {
|
||||
*[Symbol.iterator](): Generator<number, void, void> {
|
||||
if (this.height > 0) {
|
||||
for (let i = 0; i < this.keyCount; i += 1) {
|
||||
yield* this.children[i]!;
|
||||
|
@ -331,27 +331,31 @@ export class BTreeNode {
|
|||
}
|
||||
|
||||
export class BTree {
|
||||
private _order: number;
|
||||
public get order() {
|
||||
return this._order;
|
||||
#order: number;
|
||||
get order() {
|
||||
return this.#order;
|
||||
}
|
||||
|
||||
private _root: BTreeNode;
|
||||
|
||||
private _size = 0;
|
||||
public get size() {
|
||||
return this._size;
|
||||
#root: BTreeNode;
|
||||
/** @internal */
|
||||
get root() {
|
||||
return this.#root;
|
||||
}
|
||||
|
||||
public constructor(order: number) {
|
||||
this._order = order;
|
||||
#size = 0;
|
||||
get size() {
|
||||
return this.#size;
|
||||
}
|
||||
|
||||
constructor(order: number) {
|
||||
this.#order = order;
|
||||
const keys = new Int32Array(order - 1);
|
||||
const children = new Array<BTreeNode>(order);
|
||||
this._root = new BTreeNode(order, keys, 0, 0, children);
|
||||
this.#root = new BTreeNode(order, keys, 0, 0, children);
|
||||
}
|
||||
|
||||
public has(value: number) {
|
||||
let node = this._root;
|
||||
has(value: number) {
|
||||
let node = this.#root;
|
||||
while (true) {
|
||||
const index = node.search(value);
|
||||
if (index >= 0) {
|
||||
|
@ -365,50 +369,50 @@ export class BTree {
|
|||
}
|
||||
}
|
||||
|
||||
public add(value: number) {
|
||||
const split = this._root.add(value);
|
||||
add(value: number) {
|
||||
const split = this.#root.add(value);
|
||||
if (typeof split === "object") {
|
||||
const keys = new Int32Array(this._order - 1);
|
||||
const keys = new Int32Array(this.#order - 1);
|
||||
keys[0] = split.key;
|
||||
|
||||
const children = new Array<BTreeNode>(this._order);
|
||||
children[0] = this._root;
|
||||
const children = new Array<BTreeNode>(this.#order);
|
||||
children[0] = this.#root;
|
||||
children[1] = split.child;
|
||||
|
||||
this._root = new BTreeNode(
|
||||
this._order,
|
||||
this.#root = new BTreeNode(
|
||||
this.#order,
|
||||
keys,
|
||||
1,
|
||||
this._root.height + 1,
|
||||
this.#root.height + 1,
|
||||
children,
|
||||
);
|
||||
}
|
||||
if (split) {
|
||||
this._size += 1;
|
||||
this.#size += 1;
|
||||
}
|
||||
return !!split;
|
||||
}
|
||||
|
||||
public delete(value: number) {
|
||||
const deleted = this._root.delete(value);
|
||||
delete(value: number) {
|
||||
const deleted = this.#root.delete(value);
|
||||
if (deleted) {
|
||||
if (this._root.height > 0 && this._root.keyCount === 0) {
|
||||
this._root = this._root.children[0]!;
|
||||
if (this.#root.height > 0 && this.#root.keyCount === 0) {
|
||||
this.#root = this.#root.children[0]!;
|
||||
}
|
||||
this._size -= 1;
|
||||
this.#size -= 1;
|
||||
}
|
||||
return deleted;
|
||||
}
|
||||
|
||||
public clear() {
|
||||
this._root.keyCount = 0;
|
||||
this._root.height = 0;
|
||||
clear() {
|
||||
this.#root.keyCount = 0;
|
||||
this.#root.height = 0;
|
||||
// immediately release all references
|
||||
this._root.children = new Array<BTreeNode>(this._order);
|
||||
this._size = 0;
|
||||
this.#root.children = new Array<BTreeNode>(this.#order);
|
||||
this.#size = 0;
|
||||
}
|
||||
|
||||
public [Symbol.iterator]() {
|
||||
return this._root[Symbol.iterator]();
|
||||
[Symbol.iterator]() {
|
||||
return this.#root[Symbol.iterator]();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,28 +3,28 @@ export interface Disposable {
|
|||
}
|
||||
|
||||
export class AutoDisposable implements Disposable {
|
||||
private disposables: Disposable[] = [];
|
||||
#disposables: Disposable[] = [];
|
||||
|
||||
public constructor() {
|
||||
constructor() {
|
||||
this.dispose = this.dispose.bind(this);
|
||||
}
|
||||
|
||||
protected addDisposable<T extends Disposable>(disposable: T): T {
|
||||
this.disposables.push(disposable);
|
||||
this.#disposables.push(disposable);
|
||||
return disposable;
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
for (const disposable of this.disposables) {
|
||||
dispose() {
|
||||
for (const disposable of this.#disposables) {
|
||||
disposable.dispose();
|
||||
}
|
||||
|
||||
this.disposables = [];
|
||||
this.#disposables = [];
|
||||
}
|
||||
}
|
||||
|
||||
export class DisposableList extends AutoDisposable {
|
||||
public add<T extends Disposable>(disposable: T): T {
|
||||
add<T extends Disposable>(disposable: T): T {
|
||||
return this.addDisposable(disposable);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ export interface AddEventListener<TEvent, TResult = unknown> {
|
|||
export class EventEmitter<TEvent, TResult = unknown> implements Disposable {
|
||||
protected readonly listeners: EventListenerInfo<TEvent, TResult>[] = [];
|
||||
|
||||
public constructor() {
|
||||
constructor() {
|
||||
this.event = this.event.bind(this);
|
||||
}
|
||||
|
||||
|
@ -42,10 +42,7 @@ export class EventEmitter<TEvent, TResult = unknown> implements Disposable {
|
|||
return remove;
|
||||
}
|
||||
|
||||
public event: AddEventListener<TEvent, TResult> = <
|
||||
TThis,
|
||||
TArgs extends unknown[],
|
||||
>(
|
||||
event: AddEventListener<TEvent, TResult> = <TThis, TArgs extends unknown[]>(
|
||||
listener: EventListener<TEvent, TThis, TArgs, TResult>,
|
||||
thisArg?: TThis,
|
||||
...args: TArgs
|
||||
|
@ -63,13 +60,13 @@ export class EventEmitter<TEvent, TResult = unknown> implements Disposable {
|
|||
return this.addEventListener(info);
|
||||
};
|
||||
|
||||
public fire(e: TEvent) {
|
||||
fire(e: TEvent) {
|
||||
for (const info of this.listeners.slice()) {
|
||||
info.listener.apply(info.thisArg, [e, ...info.args]);
|
||||
}
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
dispose() {
|
||||
this.listeners.length = 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
export abstract class PcmPlayer<T> {
|
||||
protected abstract sourceName: string;
|
||||
|
||||
private _context: AudioContext;
|
||||
private _worklet: AudioWorkletNode | undefined;
|
||||
private _buffer: T[] = [];
|
||||
#context: AudioContext;
|
||||
#worklet: AudioWorkletNode | undefined;
|
||||
#buffers: T[] = [];
|
||||
|
||||
constructor(sampleRate: number) {
|
||||
this._context = new AudioContext({
|
||||
this.#context = new AudioContext({
|
||||
latencyHint: "interactive",
|
||||
sampleRate,
|
||||
});
|
||||
|
@ -14,38 +14,38 @@ export abstract class PcmPlayer<T> {
|
|||
|
||||
protected abstract feedCore(worklet: AudioWorkletNode, source: T): void;
|
||||
|
||||
public feed(source: T) {
|
||||
if (this._worklet === undefined) {
|
||||
this._buffer.push(source);
|
||||
feed(source: T) {
|
||||
if (this.#worklet === undefined) {
|
||||
this.#buffers.push(source);
|
||||
return;
|
||||
}
|
||||
|
||||
this.feedCore(this._worklet, source);
|
||||
this.feedCore(this.#worklet, source);
|
||||
}
|
||||
|
||||
public async start() {
|
||||
await this._context.audioWorklet.addModule(
|
||||
async start() {
|
||||
await this.#context.audioWorklet.addModule(
|
||||
new URL("./worker.js", import.meta.url),
|
||||
);
|
||||
|
||||
this._worklet = new AudioWorkletNode(this._context, this.sourceName, {
|
||||
this.#worklet = new AudioWorkletNode(this.#context, this.sourceName, {
|
||||
numberOfInputs: 0,
|
||||
numberOfOutputs: 1,
|
||||
outputChannelCount: [2],
|
||||
});
|
||||
this._worklet.connect(this._context.destination);
|
||||
this.#worklet.connect(this.#context.destination);
|
||||
|
||||
for (const source of this._buffer) {
|
||||
this.feedCore(this._worklet, source);
|
||||
for (const source of this.#buffers) {
|
||||
this.feedCore(this.#worklet, source);
|
||||
}
|
||||
this._buffer.length = 0;
|
||||
this.#buffers.length = 0;
|
||||
}
|
||||
|
||||
async stop() {
|
||||
this._worklet?.disconnect();
|
||||
this._worklet = undefined;
|
||||
this.#worklet?.disconnect();
|
||||
this.#worklet = undefined;
|
||||
|
||||
await this._context.close();
|
||||
await this.#context.close();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,16 +2,16 @@ abstract class SourceProcessor<T>
|
|||
extends AudioWorkletProcessor
|
||||
implements AudioWorkletProcessorImpl
|
||||
{
|
||||
private _sources: T[] = [];
|
||||
private _sourceSampleCount = 0;
|
||||
#sources: T[] = [];
|
||||
#sourceSampleCount = 0;
|
||||
|
||||
public constructor() {
|
||||
constructor() {
|
||||
super();
|
||||
this.port.onmessage = (event) => {
|
||||
const data = event.data as ArrayBuffer[];
|
||||
const [source, length] = this.createSource(data);
|
||||
this._sources.push(source);
|
||||
this._sourceSampleCount += length;
|
||||
this.#sources.push(source);
|
||||
this.#sourceSampleCount += length;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -26,13 +26,13 @@ abstract class SourceProcessor<T>
|
|||
// Resample source catch up with output
|
||||
// TODO: should we limit the minimum and maximum speed?
|
||||
// TODO: this simple resample method changes pitch
|
||||
const sourceIndexStep = this._sourceSampleCount > 48000 ? 1.02 : 1;
|
||||
const sourceIndexStep = this.#sourceSampleCount > 48000 ? 1.02 : 1;
|
||||
let sourceIndex = 0;
|
||||
|
||||
while (this._sources.length > 0 && outputIndex < outputLength) {
|
||||
while (this.#sources.length > 0 && outputIndex < outputLength) {
|
||||
const beginSourceIndex = sourceIndex | 0;
|
||||
|
||||
let source: T | undefined = this._sources[0];
|
||||
let source: T | undefined = this.#sources[0];
|
||||
[source, sourceIndex, outputIndex] = this.copyChunk(
|
||||
sourceIndex,
|
||||
sourceIndexStep,
|
||||
|
@ -44,16 +44,16 @@ abstract class SourceProcessor<T>
|
|||
);
|
||||
|
||||
const consumedSampleCount = (sourceIndex | 0) - beginSourceIndex;
|
||||
this._sourceSampleCount -= consumedSampleCount;
|
||||
this.#sourceSampleCount -= consumedSampleCount;
|
||||
sourceIndex -= consumedSampleCount;
|
||||
|
||||
if (source) {
|
||||
// Output full
|
||||
this._sources[0] = source;
|
||||
this.#sources[0] = source;
|
||||
return true;
|
||||
}
|
||||
|
||||
this._sources.shift();
|
||||
this.#sources.shift();
|
||||
}
|
||||
|
||||
if (outputIndex < outputLength) {
|
||||
|
@ -98,7 +98,7 @@ class Int16SourceProcessor
|
|||
): [
|
||||
source: Int16Array | undefined,
|
||||
sourceIndex: number,
|
||||
outputIndex: number
|
||||
outputIndex: number,
|
||||
] {
|
||||
const sourceLength = source.length;
|
||||
let sourceSampleIndex = sourceIndex << 1;
|
||||
|
@ -145,7 +145,7 @@ class Float32SourceProcessor extends SourceProcessor<Float32Array> {
|
|||
): [
|
||||
source: Float32Array | undefined,
|
||||
sourceIndex: number,
|
||||
outputIndex: number
|
||||
outputIndex: number,
|
||||
] {
|
||||
const sourceLength = source.length;
|
||||
let sourceSampleIndex = sourceIndex << 1;
|
||||
|
@ -192,7 +192,7 @@ class Float32PlanerSourceProcessor extends SourceProcessor<Float32Array[]> {
|
|||
): [
|
||||
source: Float32Array[] | undefined,
|
||||
sourceIndex: number,
|
||||
outputIndex: number
|
||||
outputIndex: number,
|
||||
] {
|
||||
const sourceLeft = source[0]!;
|
||||
const sourceRight = source[1]!;
|
||||
|
|
|
@ -38,56 +38,54 @@ function initialize() {
|
|||
}
|
||||
|
||||
export class TinyH264Decoder implements ScrcpyVideoDecoder {
|
||||
public static readonly capabilities: Record<
|
||||
string,
|
||||
ScrcpyVideoDecoderCapability
|
||||
> = {
|
||||
static readonly capabilities: Record<string, ScrcpyVideoDecoderCapability> =
|
||||
{
|
||||
h264: {
|
||||
maxProfile: AndroidAvcProfile.Baseline,
|
||||
maxLevel: AndroidAvcLevel.Level4,
|
||||
},
|
||||
};
|
||||
|
||||
private _renderer: HTMLCanvasElement;
|
||||
public get renderer() {
|
||||
return this._renderer;
|
||||
#renderer: HTMLCanvasElement;
|
||||
get renderer() {
|
||||
return this.#renderer;
|
||||
}
|
||||
|
||||
private _frameRendered = 0;
|
||||
public get frameRendered() {
|
||||
return this._frameRendered;
|
||||
#frameRendered = 0;
|
||||
get frameRendered() {
|
||||
return this.#frameRendered;
|
||||
}
|
||||
|
||||
private _frameSkipped = 0;
|
||||
public get frameSkipped() {
|
||||
return this._frameSkipped;
|
||||
#frameSkipped = 0;
|
||||
get frameSkipped() {
|
||||
return this.#frameSkipped;
|
||||
}
|
||||
|
||||
private _writable: WritableStream<ScrcpyMediaStreamPacket>;
|
||||
public get writable() {
|
||||
return this._writable;
|
||||
#writable: WritableStream<ScrcpyMediaStreamPacket>;
|
||||
get writable() {
|
||||
return this.#writable;
|
||||
}
|
||||
|
||||
private _yuvCanvas: YuvCanvas | undefined;
|
||||
private _initializer: PromiseResolver<TinyH264Wrapper> | undefined;
|
||||
#yuvCanvas: YuvCanvas | undefined;
|
||||
#initializer: PromiseResolver<TinyH264Wrapper> | undefined;
|
||||
|
||||
public constructor() {
|
||||
constructor() {
|
||||
void initialize();
|
||||
|
||||
this._renderer = document.createElement("canvas");
|
||||
this.#renderer = document.createElement("canvas");
|
||||
|
||||
this._writable = new WritableStream<ScrcpyMediaStreamPacket>({
|
||||
this.#writable = new WritableStream<ScrcpyMediaStreamPacket>({
|
||||
write: async (packet) => {
|
||||
switch (packet.type) {
|
||||
case "configuration":
|
||||
await this.configure(packet.data);
|
||||
await this.#configure(packet.data);
|
||||
break;
|
||||
case "data": {
|
||||
if (!this._initializer) {
|
||||
if (!this.#initializer) {
|
||||
throw new Error("Decoder not configured");
|
||||
}
|
||||
|
||||
const wrapper = await this._initializer.promise;
|
||||
const wrapper = await this.#initializer.promise;
|
||||
wrapper.feed(packet.data.slice().buffer);
|
||||
break;
|
||||
}
|
||||
|
@ -96,14 +94,14 @@ export class TinyH264Decoder implements ScrcpyVideoDecoder {
|
|||
});
|
||||
}
|
||||
|
||||
private async configure(data: Uint8Array) {
|
||||
async #configure(data: Uint8Array) {
|
||||
this.dispose();
|
||||
|
||||
this._initializer = new PromiseResolver<TinyH264Wrapper>();
|
||||
this.#initializer = new PromiseResolver<TinyH264Wrapper>();
|
||||
const { YuvBuffer, YuvCanvas } = await initialize();
|
||||
|
||||
if (!this._yuvCanvas) {
|
||||
this._yuvCanvas = YuvCanvas.attach(this._renderer);
|
||||
if (!this.#yuvCanvas) {
|
||||
this.#yuvCanvas = YuvCanvas.attach(this.#renderer);
|
||||
}
|
||||
|
||||
const {
|
||||
|
@ -134,12 +132,12 @@ export class TinyH264Decoder implements ScrcpyVideoDecoder {
|
|||
});
|
||||
|
||||
const wrapper = await createTinyH264Wrapper();
|
||||
this._initializer.resolve(wrapper);
|
||||
this.#initializer.resolve(wrapper);
|
||||
|
||||
const uPlaneOffset = encodedWidth * encodedHeight;
|
||||
const vPlaneOffset = uPlaneOffset + chromaWidth * chromaHeight;
|
||||
wrapper.onPictureReady(({ data }) => {
|
||||
this._frameRendered += 1;
|
||||
this.#frameRendered += 1;
|
||||
const array = new Uint8Array(data);
|
||||
const frame = YuvBuffer.frame(
|
||||
format,
|
||||
|
@ -147,17 +145,17 @@ export class TinyH264Decoder implements ScrcpyVideoDecoder {
|
|||
YuvBuffer.chromaPlane(format, array, chromaWidth, uPlaneOffset),
|
||||
YuvBuffer.chromaPlane(format, array, chromaWidth, vPlaneOffset),
|
||||
);
|
||||
this._yuvCanvas!.drawFrame(frame);
|
||||
this.#yuvCanvas!.drawFrame(frame);
|
||||
});
|
||||
|
||||
wrapper.feed(data.slice().buffer);
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this._initializer?.promise
|
||||
dispose(): void {
|
||||
this.#initializer?.promise
|
||||
.then((wrapper) => wrapper.dispose())
|
||||
// NOOP: It's disposed so nobody cares about the error
|
||||
.catch(NOOP);
|
||||
this._initializer = undefined;
|
||||
this.#initializer = undefined;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -146,8 +146,8 @@ declare module "yuv-canvas" {
|
|||
import type { YUVFrame } from "yuv-buffer";
|
||||
|
||||
export default class YUVCanvas {
|
||||
public static attach(canvas: HTMLCanvasElement): YUVCanvas;
|
||||
static attach(canvas: HTMLCanvasElement): YUVCanvas;
|
||||
|
||||
public drawFrame(data: YUVFrame): void;
|
||||
drawFrame(data: YUVFrame): void;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { PromiseResolver } from "@yume-chan/async";
|
||||
import { AutoDisposable, EventEmitter } from "@yume-chan/event";
|
||||
import type { Disposable } from "@yume-chan/event";
|
||||
import { AutoDisposable, EventEmitter } from "@yume-chan/event";
|
||||
|
||||
let worker: Worker | undefined;
|
||||
let workerReady = false;
|
||||
|
@ -36,28 +36,27 @@ function subscribePictureReady(
|
|||
}
|
||||
|
||||
export class TinyH264Wrapper extends AutoDisposable {
|
||||
public readonly streamId: number;
|
||||
readonly streamId: number;
|
||||
|
||||
private readonly pictureReadyEvent =
|
||||
new EventEmitter<PictureReadyEventArgs>();
|
||||
public get onPictureReady() {
|
||||
return this.pictureReadyEvent.event;
|
||||
readonly #pictureReadyEvent = new EventEmitter<PictureReadyEventArgs>();
|
||||
get onPictureReady() {
|
||||
return this.#pictureReadyEvent.event;
|
||||
}
|
||||
|
||||
public constructor(streamId: number) {
|
||||
constructor(streamId: number) {
|
||||
super();
|
||||
|
||||
this.streamId = streamId;
|
||||
this.addDisposable(
|
||||
subscribePictureReady(streamId, this.handlePictureReady),
|
||||
subscribePictureReady(streamId, this.#handlePictureReady),
|
||||
);
|
||||
}
|
||||
|
||||
private handlePictureReady = (e: PictureReadyEventArgs) => {
|
||||
this.pictureReadyEvent.fire(e);
|
||||
#handlePictureReady = (e: PictureReadyEventArgs) => {
|
||||
this.#pictureReadyEvent.fire(e);
|
||||
};
|
||||
|
||||
public feed(data: ArrayBuffer) {
|
||||
feed(data: ArrayBuffer) {
|
||||
worker!.postMessage(
|
||||
{
|
||||
type: "decode",
|
||||
|
@ -70,7 +69,7 @@ export class TinyH264Wrapper extends AutoDisposable {
|
|||
);
|
||||
}
|
||||
|
||||
public override dispose() {
|
||||
override dispose() {
|
||||
super.dispose();
|
||||
worker!.postMessage({
|
||||
type: "release",
|
||||
|
|
|
@ -27,63 +27,61 @@ function toUint32Le(data: Uint8Array, offset: number) {
|
|||
}
|
||||
|
||||
export class WebCodecsDecoder implements ScrcpyVideoDecoder {
|
||||
public static isSupported() {
|
||||
static isSupported() {
|
||||
return typeof globalThis.VideoDecoder !== "undefined";
|
||||
}
|
||||
|
||||
public static readonly capabilities: Record<
|
||||
string,
|
||||
ScrcpyVideoDecoderCapability
|
||||
> = {
|
||||
static readonly capabilities: Record<string, ScrcpyVideoDecoderCapability> =
|
||||
{
|
||||
h264: {},
|
||||
h265: {},
|
||||
};
|
||||
|
||||
private _codec: ScrcpyVideoCodecId;
|
||||
public get codec() {
|
||||
return this._codec;
|
||||
#codec: ScrcpyVideoCodecId;
|
||||
get codec() {
|
||||
return this.#codec;
|
||||
}
|
||||
|
||||
private _writable: WritableStream<ScrcpyMediaStreamPacket>;
|
||||
public get writable() {
|
||||
return this._writable;
|
||||
#writable: WritableStream<ScrcpyMediaStreamPacket>;
|
||||
get writable() {
|
||||
return this.#writable;
|
||||
}
|
||||
|
||||
private _renderer: HTMLCanvasElement;
|
||||
public get renderer() {
|
||||
return this._renderer;
|
||||
#renderer: HTMLCanvasElement;
|
||||
get renderer() {
|
||||
return this.#renderer;
|
||||
}
|
||||
|
||||
private _frameRendered = 0;
|
||||
public get frameRendered() {
|
||||
return this._frameRendered;
|
||||
#frameRendered = 0;
|
||||
get frameRendered() {
|
||||
return this.#frameRendered;
|
||||
}
|
||||
|
||||
private _frameSkipped = 0;
|
||||
public get frameSkipped() {
|
||||
return this._frameSkipped;
|
||||
#frameSkipped = 0;
|
||||
get frameSkipped() {
|
||||
return this.#frameSkipped;
|
||||
}
|
||||
|
||||
private context: CanvasRenderingContext2D;
|
||||
private decoder: VideoDecoder;
|
||||
private _config: Uint8Array | undefined;
|
||||
#context: CanvasRenderingContext2D;
|
||||
#decoder: VideoDecoder;
|
||||
#config: Uint8Array | undefined;
|
||||
|
||||
private currentFrameRendered = false;
|
||||
private animationFrameId = 0;
|
||||
#currentFrameRendered = false;
|
||||
#animationFrameId = 0;
|
||||
|
||||
public constructor(codec: ScrcpyVideoCodecId) {
|
||||
this._codec = codec;
|
||||
constructor(codec: ScrcpyVideoCodecId) {
|
||||
this.#codec = codec;
|
||||
|
||||
this._renderer = document.createElement("canvas");
|
||||
this.#renderer = document.createElement("canvas");
|
||||
|
||||
this.context = this._renderer.getContext("2d")!;
|
||||
this.decoder = new VideoDecoder({
|
||||
this.#context = this.#renderer.getContext("2d")!;
|
||||
this.#decoder = new VideoDecoder({
|
||||
output: (frame) => {
|
||||
if (this.currentFrameRendered) {
|
||||
this._frameSkipped += 1;
|
||||
if (this.#currentFrameRendered) {
|
||||
this.#frameSkipped += 1;
|
||||
} else {
|
||||
this.currentFrameRendered = true;
|
||||
this._frameRendered += 1;
|
||||
this.#currentFrameRendered = true;
|
||||
this.#frameRendered += 1;
|
||||
}
|
||||
|
||||
// PERF: H.264 renderer may draw multiple frames in one vertical sync interval to minimize latency.
|
||||
|
@ -92,7 +90,7 @@ export class WebCodecsDecoder implements ScrcpyVideoDecoder {
|
|||
// But this ensures users can always see the most up-to-date screen.
|
||||
// This is also the behavior of official Scrcpy client.
|
||||
// https://github.com/Genymobile/scrcpy/issues/3679
|
||||
this.context.drawImage(frame, 0, 0);
|
||||
this.#context.drawImage(frame, 0, 0);
|
||||
frame.close();
|
||||
},
|
||||
error(e) {
|
||||
|
@ -100,29 +98,29 @@ export class WebCodecsDecoder implements ScrcpyVideoDecoder {
|
|||
},
|
||||
});
|
||||
|
||||
this._writable = new WritableStream<ScrcpyMediaStreamPacket>({
|
||||
this.#writable = new WritableStream<ScrcpyMediaStreamPacket>({
|
||||
write: (packet) => {
|
||||
switch (packet.type) {
|
||||
case "configuration":
|
||||
this.configure(packet.data);
|
||||
this.#configure(packet.data);
|
||||
break;
|
||||
case "data":
|
||||
this.decode(packet);
|
||||
this.#decode(packet);
|
||||
break;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
this.onFramePresented();
|
||||
this.#onFramePresented();
|
||||
}
|
||||
|
||||
private onFramePresented = () => {
|
||||
this.currentFrameRendered = false;
|
||||
this.animationFrameId = requestAnimationFrame(this.onFramePresented);
|
||||
#onFramePresented = () => {
|
||||
this.#currentFrameRendered = false;
|
||||
this.#animationFrameId = requestAnimationFrame(this.#onFramePresented);
|
||||
};
|
||||
|
||||
private configure(data: Uint8Array) {
|
||||
switch (this._codec) {
|
||||
#configure(data: Uint8Array) {
|
||||
switch (this.#codec) {
|
||||
case ScrcpyVideoCodecId.H264: {
|
||||
const {
|
||||
profileIndex,
|
||||
|
@ -132,15 +130,15 @@ export class WebCodecsDecoder implements ScrcpyVideoDecoder {
|
|||
croppedHeight,
|
||||
} = h264ParseConfiguration(data);
|
||||
|
||||
this._renderer.width = croppedWidth;
|
||||
this._renderer.height = croppedHeight;
|
||||
this.#renderer.width = croppedWidth;
|
||||
this.#renderer.height = croppedHeight;
|
||||
|
||||
// https://www.rfc-editor.org/rfc/rfc6381#section-3.3
|
||||
// ISO Base Media File Format Name Space
|
||||
const codec = `avc1.${[profileIndex, constraintSet, levelIndex]
|
||||
.map(toHex)
|
||||
.join("")}`;
|
||||
this.decoder.configure({
|
||||
this.#decoder.configure({
|
||||
codec: codec,
|
||||
optimizeForLatency: true,
|
||||
});
|
||||
|
@ -158,8 +156,8 @@ export class WebCodecsDecoder implements ScrcpyVideoDecoder {
|
|||
croppedHeight,
|
||||
} = h265ParseConfiguration(data);
|
||||
|
||||
this._renderer.width = croppedWidth;
|
||||
this._renderer.height = croppedHeight;
|
||||
this.#renderer.width = croppedWidth;
|
||||
this.#renderer.height = croppedHeight;
|
||||
|
||||
const codec = [
|
||||
"hev1",
|
||||
|
@ -175,36 +173,36 @@ export class WebCodecsDecoder implements ScrcpyVideoDecoder {
|
|||
.toString(16)
|
||||
.toUpperCase(),
|
||||
].join(".");
|
||||
this.decoder.configure({
|
||||
this.#decoder.configure({
|
||||
codec,
|
||||
optimizeForLatency: true,
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
this._config = data;
|
||||
this.#config = data;
|
||||
}
|
||||
|
||||
private decode(packet: ScrcpyMediaStreamDataPacket) {
|
||||
if (this.decoder.state !== "configured") {
|
||||
#decode(packet: ScrcpyMediaStreamDataPacket) {
|
||||
if (this.#decoder.state !== "configured") {
|
||||
return;
|
||||
}
|
||||
|
||||
// WebCodecs requires configuration data to be with the first frame.
|
||||
// https://www.w3.org/TR/webcodecs-avc-codec-registration/#encodedvideochunk-type
|
||||
let data: Uint8Array;
|
||||
if (this._config !== undefined) {
|
||||
if (this.#config !== undefined) {
|
||||
data = new Uint8Array(
|
||||
this._config.byteLength + packet.data.byteLength,
|
||||
this.#config.byteLength + packet.data.byteLength,
|
||||
);
|
||||
data.set(this._config, 0);
|
||||
data.set(packet.data, this._config.byteLength);
|
||||
this._config = undefined;
|
||||
data.set(this.#config, 0);
|
||||
data.set(packet.data, this.#config.byteLength);
|
||||
this.#config = undefined;
|
||||
} else {
|
||||
data = packet.data;
|
||||
}
|
||||
|
||||
this.decoder.decode(
|
||||
this.#decoder.decode(
|
||||
new EncodedVideoChunk({
|
||||
// Treat `undefined` as `key`, otherwise won't decode.
|
||||
type: packet.keyframe === false ? "delta" : "key",
|
||||
|
@ -214,10 +212,10 @@ export class WebCodecsDecoder implements ScrcpyVideoDecoder {
|
|||
);
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
cancelAnimationFrame(this.animationFrameId);
|
||||
if (this.decoder.state !== "closed") {
|
||||
this.decoder.close();
|
||||
dispose() {
|
||||
cancelAnimationFrame(this.#animationFrameId);
|
||||
if (this.#decoder.state !== "closed") {
|
||||
this.#decoder.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -231,96 +231,93 @@ export function naluRemoveEmulation(buffer: Uint8Array) {
|
|||
}
|
||||
|
||||
export class NaluSodbBitReader {
|
||||
private readonly _nalu: Uint8Array;
|
||||
private readonly _byteLength: number;
|
||||
private readonly _stopBitIndex: number;
|
||||
readonly #nalu: Uint8Array;
|
||||
readonly #byteLength: number;
|
||||
readonly #stopBitIndex: number;
|
||||
|
||||
private _zeroCount = 0;
|
||||
private _bytePosition = -1;
|
||||
private _bitPosition = -1;
|
||||
private _byte = 0;
|
||||
#zeroCount = 0;
|
||||
#bytePosition = -1;
|
||||
#bitPosition = -1;
|
||||
#byte = 0;
|
||||
|
||||
public get byteLength() {
|
||||
return this._byteLength;
|
||||
get byteLength() {
|
||||
return this.#byteLength;
|
||||
}
|
||||
|
||||
public get stopBitIndex() {
|
||||
return this._stopBitIndex;
|
||||
get stopBitIndex() {
|
||||
return this.#stopBitIndex;
|
||||
}
|
||||
|
||||
public get bytePosition() {
|
||||
return this._bytePosition;
|
||||
get bytePosition() {
|
||||
return this.#bytePosition;
|
||||
}
|
||||
|
||||
public get bitPosition() {
|
||||
return this._bitPosition;
|
||||
get bitPosition() {
|
||||
return this.#bitPosition;
|
||||
}
|
||||
|
||||
public get ended() {
|
||||
get ended() {
|
||||
return (
|
||||
this._bytePosition === this._byteLength &&
|
||||
this._bitPosition === this._stopBitIndex
|
||||
this.#bytePosition === this.#byteLength &&
|
||||
this.#bitPosition === this.#stopBitIndex
|
||||
);
|
||||
}
|
||||
|
||||
public constructor(nalu: Uint8Array) {
|
||||
this._nalu = nalu;
|
||||
constructor(nalu: Uint8Array) {
|
||||
this.#nalu = nalu;
|
||||
|
||||
for (let i = nalu.length - 1; i >= 0; i -= 1) {
|
||||
if (this._nalu[i] === 0) {
|
||||
if (this.#nalu[i] === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const byte = nalu[i]!;
|
||||
for (let j = 0; j < 8; j += 1) {
|
||||
if (((byte >> j) & 1) === 1) {
|
||||
this._byteLength = i;
|
||||
this._stopBitIndex = j;
|
||||
this.readByte();
|
||||
this.#byteLength = i;
|
||||
this.#stopBitIndex = j;
|
||||
this.#readByte();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error("End bit not found");
|
||||
throw new Error("Stop bit not found");
|
||||
}
|
||||
|
||||
private readByte() {
|
||||
this._byte = this._nalu[this._bytePosition]!;
|
||||
if (this._zeroCount === 2 && this._byte === 3) {
|
||||
this._zeroCount = 0;
|
||||
this._bytePosition += 1;
|
||||
this.readByte();
|
||||
#readByte() {
|
||||
this.#byte = this.#nalu[this.#bytePosition]!;
|
||||
if (this.#zeroCount === 2 && this.#byte === 3) {
|
||||
this.#zeroCount = 0;
|
||||
this.#bytePosition += 1;
|
||||
this.#readByte();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._byte === 0) {
|
||||
this._zeroCount += 1;
|
||||
if (this.#byte === 0) {
|
||||
this.#zeroCount += 1;
|
||||
} else {
|
||||
this._zeroCount = 0;
|
||||
this.#zeroCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public next() {
|
||||
if (this._bitPosition === -1) {
|
||||
this._bitPosition = 7;
|
||||
this._bytePosition += 1;
|
||||
this.readByte();
|
||||
next() {
|
||||
if (this.#bitPosition === -1) {
|
||||
this.#bitPosition = 7;
|
||||
this.#bytePosition += 1;
|
||||
this.#readByte();
|
||||
}
|
||||
|
||||
if (
|
||||
this._bytePosition === this._byteLength &&
|
||||
this._bitPosition === this._stopBitIndex
|
||||
) {
|
||||
if (this.ended) {
|
||||
throw new Error("Bit index out of bounds");
|
||||
}
|
||||
|
||||
const value = (this._byte >> this._bitPosition) & 1;
|
||||
this._bitPosition -= 1;
|
||||
const value = (this.#byte >> this.#bitPosition) & 1;
|
||||
this.#bitPosition -= 1;
|
||||
return value;
|
||||
}
|
||||
|
||||
public read(length: number): number {
|
||||
read(length: number): number {
|
||||
if (length > 32) {
|
||||
throw new Error("Read length too large");
|
||||
}
|
||||
|
@ -332,13 +329,13 @@ export class NaluSodbBitReader {
|
|||
return result;
|
||||
}
|
||||
|
||||
public skip(length: number) {
|
||||
skip(length: number) {
|
||||
for (let i = 0; i < length; i += 1) {
|
||||
this.next();
|
||||
}
|
||||
}
|
||||
|
||||
public decodeExponentialGolombNumber(): number {
|
||||
decodeExponentialGolombNumber(): number {
|
||||
let length = 0;
|
||||
while (this.next() === 0) {
|
||||
length += 1;
|
||||
|
@ -349,14 +346,35 @@ export class NaluSodbBitReader {
|
|||
return ((1 << length) | this.read(length)) - 1;
|
||||
}
|
||||
|
||||
public peek(length: number) {
|
||||
const { _zeroCount, _bytePosition, _bitPosition, _byte } = this;
|
||||
#save() {
|
||||
return {
|
||||
zeroCount: this.#zeroCount,
|
||||
bytePosition: this.#bytePosition,
|
||||
bitPosition: this.#bitPosition,
|
||||
byte: this.#byte,
|
||||
};
|
||||
}
|
||||
|
||||
#restore(state: {
|
||||
zeroCount: number;
|
||||
bytePosition: number;
|
||||
bitPosition: number;
|
||||
byte: number;
|
||||
}) {
|
||||
this.#zeroCount = state.zeroCount;
|
||||
this.#bytePosition = state.bytePosition;
|
||||
this.#bitPosition = state.bitPosition;
|
||||
this.#byte = state.byte;
|
||||
}
|
||||
|
||||
peek(length: number) {
|
||||
const state = this.#save();
|
||||
const result = this.read(length);
|
||||
Object.assign(this, { _zeroCount, _bytePosition, _bitPosition, _byte });
|
||||
this.#restore(state);
|
||||
return result;
|
||||
}
|
||||
|
||||
public readBytes(length: number): Uint8Array {
|
||||
readBytes(length: number): Uint8Array {
|
||||
const result = new Uint8Array(length);
|
||||
for (let i = 0; i < length; i += 1) {
|
||||
result[i] = this.read(8);
|
||||
|
@ -364,10 +382,10 @@ export class NaluSodbBitReader {
|
|||
return result;
|
||||
}
|
||||
|
||||
public peekBytes(length: number): Uint8Array {
|
||||
const { _zeroCount, _bytePosition, _bitPosition, _byte } = this;
|
||||
peekBytes(length: number): Uint8Array {
|
||||
const state = this.#save();
|
||||
const result = this.readBytes(length);
|
||||
Object.assign(this, { _zeroCount, _bytePosition, _bitPosition, _byte });
|
||||
this.#restore(state);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,12 +15,11 @@ import { ScrcpyControlMessageType } from "./type.js";
|
|||
* so Scrcpy server can remove the previously hovering pointer.
|
||||
*/
|
||||
export class ScrcpyHoverHelper {
|
||||
// AFAIK, only mouse and pen can have hover state
|
||||
// and you can't have two mouses or pens.
|
||||
// So remember the last hovering pointer is enough.
|
||||
private lastHoverMessage: ScrcpyInjectTouchControlMessage | undefined;
|
||||
// There can be only one hovering pointer (either mouse or pen,
|
||||
// touch can have multiple pointers but no hovering state).
|
||||
#lastHoverMessage: ScrcpyInjectTouchControlMessage | undefined;
|
||||
|
||||
public process(
|
||||
process(
|
||||
message: Omit<ScrcpyInjectTouchControlMessage, "type">,
|
||||
): ScrcpyInjectTouchControlMessage[] {
|
||||
const result: ScrcpyInjectTouchControlMessage[] = [];
|
||||
|
@ -28,21 +27,21 @@ export class ScrcpyHoverHelper {
|
|||
// A different pointer appeared,
|
||||
// Cancel previously hovering pointer so Scrcpy server can free up the pointer ID.
|
||||
if (
|
||||
this.lastHoverMessage &&
|
||||
this.lastHoverMessage.pointerId !== message.pointerId
|
||||
this.#lastHoverMessage &&
|
||||
this.#lastHoverMessage.pointerId !== message.pointerId
|
||||
) {
|
||||
// TODO: Inject MotionEvent.ACTION_HOVER_EXIT
|
||||
// From testing, it seems no App cares about this event.
|
||||
result.push({
|
||||
...this.lastHoverMessage,
|
||||
...this.#lastHoverMessage,
|
||||
action: AndroidMotionEventAction.Up,
|
||||
});
|
||||
this.lastHoverMessage = undefined;
|
||||
this.#lastHoverMessage = undefined;
|
||||
}
|
||||
|
||||
if (message.action === AndroidMotionEventAction.HoverMove) {
|
||||
// TODO: Inject MotionEvent.ACTION_HOVER_ENTER
|
||||
this.lastHoverMessage = message as ScrcpyInjectTouchControlMessage;
|
||||
this.#lastHoverMessage = message as ScrcpyInjectTouchControlMessage;
|
||||
}
|
||||
|
||||
(message as ScrcpyInjectTouchControlMessage).type =
|
||||
|
|
|
@ -17,40 +17,38 @@ import {
|
|||
} from "./type.js";
|
||||
|
||||
export class ScrcpyControlMessageSerializer {
|
||||
private _options: ScrcpyOptions<object>;
|
||||
private _typeValues: ScrcpyControlMessageTypeValue;
|
||||
private _scrollController: ScrcpyScrollController;
|
||||
#options: ScrcpyOptions<object>;
|
||||
#typeValues: ScrcpyControlMessageTypeValue;
|
||||
#scrollController: ScrcpyScrollController;
|
||||
|
||||
public constructor(options: ScrcpyOptions<object>) {
|
||||
this._options = options;
|
||||
this._typeValues = new ScrcpyControlMessageTypeValue(options);
|
||||
this._scrollController = options.createScrollController();
|
||||
constructor(options: ScrcpyOptions<object>) {
|
||||
this.#options = options;
|
||||
this.#typeValues = new ScrcpyControlMessageTypeValue(options);
|
||||
this.#scrollController = options.createScrollController();
|
||||
}
|
||||
|
||||
public injectKeyCode(
|
||||
message: Omit<ScrcpyInjectKeyCodeControlMessage, "type">,
|
||||
) {
|
||||
injectKeyCode(message: Omit<ScrcpyInjectKeyCodeControlMessage, "type">) {
|
||||
return ScrcpyInjectKeyCodeControlMessage.serialize(
|
||||
this._typeValues.fillMessageType(
|
||||
this.#typeValues.fillMessageType(
|
||||
message,
|
||||
ScrcpyControlMessageType.InjectKeyCode,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
public injectText(text: string) {
|
||||
injectText(text: string) {
|
||||
return ScrcpyInjectTextControlMessage.serialize({
|
||||
text,
|
||||
type: this._typeValues.get(ScrcpyControlMessageType.InjectText),
|
||||
type: this.#typeValues.get(ScrcpyControlMessageType.InjectText),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* `pressure` is a float value between 0 and 1.
|
||||
*/
|
||||
public injectTouch(message: Omit<ScrcpyInjectTouchControlMessage, "type">) {
|
||||
return this._options.serializeInjectTouchControlMessage(
|
||||
this._typeValues.fillMessageType(
|
||||
injectTouch(message: Omit<ScrcpyInjectTouchControlMessage, "type">) {
|
||||
return this.#options.serializeInjectTouchControlMessage(
|
||||
this.#typeValues.fillMessageType(
|
||||
message,
|
||||
ScrcpyControlMessageType.InjectTouch,
|
||||
),
|
||||
|
@ -60,36 +58,34 @@ export class ScrcpyControlMessageSerializer {
|
|||
/**
|
||||
* `scrollX` and `scrollY` are float values between 0 and 1.
|
||||
*/
|
||||
public injectScroll(
|
||||
message: Omit<ScrcpyInjectScrollControlMessage, "type">,
|
||||
) {
|
||||
return this._scrollController.serializeScrollMessage(
|
||||
this._typeValues.fillMessageType(
|
||||
injectScroll(message: Omit<ScrcpyInjectScrollControlMessage, "type">) {
|
||||
return this.#scrollController.serializeScrollMessage(
|
||||
this.#typeValues.fillMessageType(
|
||||
message,
|
||||
ScrcpyControlMessageType.InjectScroll,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
public backOrScreenOn(action: AndroidKeyEventAction) {
|
||||
return this._options.serializeBackOrScreenOnControlMessage({
|
||||
backOrScreenOn(action: AndroidKeyEventAction) {
|
||||
return this.#options.serializeBackOrScreenOnControlMessage({
|
||||
action,
|
||||
type: this._typeValues.get(ScrcpyControlMessageType.BackOrScreenOn),
|
||||
type: this.#typeValues.get(ScrcpyControlMessageType.BackOrScreenOn),
|
||||
});
|
||||
}
|
||||
|
||||
public setScreenPowerMode(mode: AndroidScreenPowerMode) {
|
||||
setScreenPowerMode(mode: AndroidScreenPowerMode) {
|
||||
return ScrcpySetScreenPowerModeControlMessage.serialize({
|
||||
mode,
|
||||
type: this._typeValues.get(
|
||||
type: this.#typeValues.get(
|
||||
ScrcpyControlMessageType.SetScreenPowerMode,
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
public rotateDevice() {
|
||||
rotateDevice() {
|
||||
return ScrcpyRotateDeviceControlMessage.serialize({
|
||||
type: this._typeValues.get(ScrcpyControlMessageType.RotateDevice),
|
||||
type: this.#typeValues.get(ScrcpyControlMessageType.RotateDevice),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,21 +25,21 @@ export enum ScrcpyControlMessageType {
|
|||
* This class provides a way to get the actual value for a given type.
|
||||
*/
|
||||
export class ScrcpyControlMessageTypeValue {
|
||||
private types: readonly ScrcpyControlMessageType[];
|
||||
#types: readonly ScrcpyControlMessageType[];
|
||||
|
||||
public constructor(options: ScrcpyOptions<object>) {
|
||||
this.types = options.controlMessageTypes;
|
||||
constructor(options: ScrcpyOptions<object>) {
|
||||
this.#types = options.controlMessageTypes;
|
||||
}
|
||||
|
||||
public get(type: ScrcpyControlMessageType): number {
|
||||
const value = this.types.indexOf(type);
|
||||
get(type: ScrcpyControlMessageType): number {
|
||||
const value = this.#types.indexOf(type);
|
||||
if (value === -1) {
|
||||
throw new Error("Not supported");
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public fillMessageType<T extends { type: ScrcpyControlMessageType }>(
|
||||
fillMessageType<T extends { type: ScrcpyControlMessageType }>(
|
||||
message: Omit<T, "type">,
|
||||
type: T["type"],
|
||||
): T {
|
||||
|
|
|
@ -16,72 +16,70 @@ import { ScrcpyControlMessageSerializer } from "./serializer.js";
|
|||
import type { AndroidScreenPowerMode } from "./set-screen-power-mode.js";
|
||||
|
||||
export class ScrcpyControlMessageWriter {
|
||||
private _writer: WritableStreamDefaultWriter<Consumable<Uint8Array>>;
|
||||
private _serializer: ScrcpyControlMessageSerializer;
|
||||
#writer: WritableStreamDefaultWriter<Consumable<Uint8Array>>;
|
||||
#serializer: ScrcpyControlMessageSerializer;
|
||||
|
||||
public constructor(
|
||||
constructor(
|
||||
writer: WritableStreamDefaultWriter<Consumable<Uint8Array>>,
|
||||
options: ScrcpyOptions<object>,
|
||||
) {
|
||||
this._writer = writer;
|
||||
this._serializer = new ScrcpyControlMessageSerializer(options);
|
||||
this.#writer = writer;
|
||||
this.#serializer = new ScrcpyControlMessageSerializer(options);
|
||||
}
|
||||
|
||||
private async write(message: Uint8Array) {
|
||||
await ConsumableWritableStream.write(this._writer, message);
|
||||
async #write(message: Uint8Array) {
|
||||
await ConsumableWritableStream.write(this.#writer, message);
|
||||
}
|
||||
|
||||
public async injectKeyCode(
|
||||
async injectKeyCode(
|
||||
message: Omit<ScrcpyInjectKeyCodeControlMessage, "type">,
|
||||
) {
|
||||
await this.write(this._serializer.injectKeyCode(message));
|
||||
await this.#write(this.#serializer.injectKeyCode(message));
|
||||
}
|
||||
|
||||
public async injectText(text: string) {
|
||||
await this.write(this._serializer.injectText(text));
|
||||
async injectText(text: string) {
|
||||
await this.#write(this.#serializer.injectText(text));
|
||||
}
|
||||
|
||||
/**
|
||||
* `pressure` is a float value between 0 and 1.
|
||||
*/
|
||||
public async injectTouch(
|
||||
message: Omit<ScrcpyInjectTouchControlMessage, "type">,
|
||||
) {
|
||||
await this.write(this._serializer.injectTouch(message));
|
||||
async injectTouch(message: Omit<ScrcpyInjectTouchControlMessage, "type">) {
|
||||
await this.#write(this.#serializer.injectTouch(message));
|
||||
}
|
||||
|
||||
/**
|
||||
* `scrollX` and `scrollY` are float values between 0 and 1.
|
||||
*/
|
||||
public async injectScroll(
|
||||
async injectScroll(
|
||||
message: Omit<ScrcpyInjectScrollControlMessage, "type">,
|
||||
) {
|
||||
const data = this._serializer.injectScroll(message);
|
||||
const data = this.#serializer.injectScroll(message);
|
||||
if (data) {
|
||||
await this.write(data);
|
||||
await this.#write(data);
|
||||
}
|
||||
}
|
||||
|
||||
public async backOrScreenOn(action: AndroidKeyEventAction) {
|
||||
const data = this._serializer.backOrScreenOn(action);
|
||||
async backOrScreenOn(action: AndroidKeyEventAction) {
|
||||
const data = this.#serializer.backOrScreenOn(action);
|
||||
if (data) {
|
||||
await this.write(data);
|
||||
await this.#write(data);
|
||||
}
|
||||
}
|
||||
|
||||
public async setScreenPowerMode(mode: AndroidScreenPowerMode) {
|
||||
await this.write(this._serializer.setScreenPowerMode(mode));
|
||||
async setScreenPowerMode(mode: AndroidScreenPowerMode) {
|
||||
await this.#write(this.#serializer.setScreenPowerMode(mode));
|
||||
}
|
||||
|
||||
public async rotateDevice() {
|
||||
await this.write(this._serializer.rotateDevice());
|
||||
async rotateDevice() {
|
||||
await this.#write(this.#serializer.rotateDevice());
|
||||
}
|
||||
|
||||
public releaseLock() {
|
||||
this._writer.releaseLock();
|
||||
releaseLock() {
|
||||
this.#writer.releaseLock();
|
||||
}
|
||||
|
||||
public async close() {
|
||||
await this._writer.close();
|
||||
async close() {
|
||||
await this.#writer.close();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,13 +27,13 @@ const CODEC_OPTION_TYPES: Partial<
|
|||
};
|
||||
|
||||
export class CodecOptions implements ScrcpyOptionValue {
|
||||
public value: Partial<CodecOptionsInit>;
|
||||
value: Partial<CodecOptionsInit>;
|
||||
|
||||
public constructor(value: Partial<CodecOptionsInit> = {}) {
|
||||
constructor(value: Partial<CodecOptionsInit> = {}) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public toOptionValue(): string | undefined {
|
||||
toOptionValue(): string | undefined {
|
||||
const entries = Object.entries(this.value).filter(
|
||||
([, value]) => value !== undefined,
|
||||
);
|
||||
|
|
|
@ -38,7 +38,7 @@ import type { ScrcpyScrollController } from "./scroll.js";
|
|||
import { ScrcpyScrollController1_16 } from "./scroll.js";
|
||||
|
||||
export class ScrcpyOptions1_16 implements ScrcpyOptions<ScrcpyOptionsInit1_16> {
|
||||
public static readonly DEFAULTS = {
|
||||
static readonly DEFAULTS = {
|
||||
logLevel: ScrcpyLogLevel1_16.Debug,
|
||||
maxSize: 0,
|
||||
bitRate: 8_000_000,
|
||||
|
@ -54,7 +54,7 @@ export class ScrcpyOptions1_16 implements ScrcpyOptions<ScrcpyOptionsInit1_16> {
|
|||
codecOptions: new CodecOptions(),
|
||||
} as const satisfies Required<ScrcpyOptionsInit1_16>;
|
||||
|
||||
public static readonly SERIALIZE_ORDER = [
|
||||
static readonly SERIALIZE_ORDER = [
|
||||
"logLevel",
|
||||
"maxSize",
|
||||
"bitRate",
|
||||
|
@ -70,11 +70,11 @@ export class ScrcpyOptions1_16 implements ScrcpyOptions<ScrcpyOptionsInit1_16> {
|
|||
"codecOptions",
|
||||
] as const satisfies readonly (keyof ScrcpyOptionsInit1_16)[];
|
||||
|
||||
public static serialize<T>(options: T, order: readonly (keyof T)[]) {
|
||||
static serialize<T>(options: T, order: readonly (keyof T)[]) {
|
||||
return order.map((key) => toScrcpyOptionValue(options[key], "-"));
|
||||
}
|
||||
|
||||
public static async parseCString(
|
||||
static async parseCString(
|
||||
stream: AsyncExactReadable,
|
||||
maxLength: number,
|
||||
): Promise<string> {
|
||||
|
@ -83,55 +83,51 @@ export class ScrcpyOptions1_16 implements ScrcpyOptions<ScrcpyOptionsInit1_16> {
|
|||
return result;
|
||||
}
|
||||
|
||||
public static async parseUint16BE(
|
||||
stream: AsyncExactReadable,
|
||||
): Promise<number> {
|
||||
static async parseUint16BE(stream: AsyncExactReadable): Promise<number> {
|
||||
const buffer = await stream.readExactly(NumberFieldType.Uint16.size);
|
||||
return NumberFieldType.Uint16.deserialize(buffer, false);
|
||||
}
|
||||
|
||||
public static async parseUint32BE(
|
||||
stream: AsyncExactReadable,
|
||||
): Promise<number> {
|
||||
static async parseUint32BE(stream: AsyncExactReadable): Promise<number> {
|
||||
const buffer = await stream.readExactly(NumberFieldType.Uint32.size);
|
||||
return NumberFieldType.Uint32.deserialize(buffer, false);
|
||||
}
|
||||
|
||||
public value: Required<ScrcpyOptionsInit1_16>;
|
||||
value: Required<ScrcpyOptionsInit1_16>;
|
||||
|
||||
public readonly defaults: Required<ScrcpyOptionsInit1_16> =
|
||||
readonly defaults: Required<ScrcpyOptionsInit1_16> =
|
||||
ScrcpyOptions1_16.DEFAULTS;
|
||||
|
||||
public readonly controlMessageTypes: readonly ScrcpyControlMessageType[] =
|
||||
readonly controlMessageTypes: readonly ScrcpyControlMessageType[] =
|
||||
SCRCPY_CONTROL_MESSAGE_TYPES_1_16;
|
||||
|
||||
public constructor(init: ScrcpyOptionsInit1_16) {
|
||||
constructor(init: ScrcpyOptionsInit1_16) {
|
||||
this.value = { ...ScrcpyOptions1_16.DEFAULTS, ...init };
|
||||
}
|
||||
|
||||
public serialize(): string[] {
|
||||
serialize(): string[] {
|
||||
return ScrcpyOptions1_16.serialize(
|
||||
this.value,
|
||||
ScrcpyOptions1_16.SERIALIZE_ORDER,
|
||||
);
|
||||
}
|
||||
|
||||
public setListEncoders(): void {
|
||||
setListEncoders(): void {
|
||||
throw new Error("Not supported");
|
||||
}
|
||||
|
||||
public setListDisplays(): void {
|
||||
setListDisplays(): void {
|
||||
// Set to an invalid value
|
||||
// Server will print valid values before crashing
|
||||
// (server will crash before opening sockets)
|
||||
this.value.displayId = -1;
|
||||
}
|
||||
|
||||
public parseEncoder(): ScrcpyEncoder | undefined {
|
||||
parseEncoder(): ScrcpyEncoder | undefined {
|
||||
throw new Error("Not supported");
|
||||
}
|
||||
|
||||
public parseDisplay(line: string): ScrcpyDisplay | undefined {
|
||||
parseDisplay(line: string): ScrcpyDisplay | undefined {
|
||||
const displayIdRegex = /\s+scrcpy --display (\d+)/;
|
||||
const match = line.match(displayIdRegex);
|
||||
if (match) {
|
||||
|
@ -142,7 +138,7 @@ export class ScrcpyOptions1_16 implements ScrcpyOptions<ScrcpyOptionsInit1_16> {
|
|||
return undefined;
|
||||
}
|
||||
|
||||
public parseVideoStreamMetadata(
|
||||
parseVideoStreamMetadata(
|
||||
stream: ReadableStream<Uint8Array>,
|
||||
): ValueOrPromise<ScrcpyVideoStream> {
|
||||
return (async () => {
|
||||
|
@ -160,11 +156,11 @@ export class ScrcpyOptions1_16 implements ScrcpyOptions<ScrcpyOptionsInit1_16> {
|
|||
})();
|
||||
}
|
||||
|
||||
public parseAudioStreamMetadata(): never {
|
||||
parseAudioStreamMetadata(): never {
|
||||
throw new Error("Not supported");
|
||||
}
|
||||
|
||||
public createMediaStreamTransformer(): TransformStream<
|
||||
createMediaStreamTransformer(): TransformStream<
|
||||
Uint8Array,
|
||||
ScrcpyMediaStreamPacket
|
||||
> {
|
||||
|
@ -207,13 +203,13 @@ export class ScrcpyOptions1_16 implements ScrcpyOptions<ScrcpyOptionsInit1_16> {
|
|||
};
|
||||
}
|
||||
|
||||
public serializeInjectTouchControlMessage(
|
||||
serializeInjectTouchControlMessage(
|
||||
message: ScrcpyInjectTouchControlMessage,
|
||||
): Uint8Array {
|
||||
return ScrcpyInjectTouchControlMessage1_16.serialize(message);
|
||||
}
|
||||
|
||||
public serializeBackOrScreenOnControlMessage(
|
||||
serializeBackOrScreenOnControlMessage(
|
||||
message: ScrcpyBackOrScreenOnControlMessage,
|
||||
) {
|
||||
if (message.action === AndroidKeyEventAction.Down) {
|
||||
|
@ -223,13 +219,13 @@ export class ScrcpyOptions1_16 implements ScrcpyOptions<ScrcpyOptionsInit1_16> {
|
|||
return undefined;
|
||||
}
|
||||
|
||||
public serializeSetClipboardControlMessage(
|
||||
serializeSetClipboardControlMessage(
|
||||
message: ScrcpySetClipboardControlMessage,
|
||||
): Uint8Array {
|
||||
return ScrcpySetClipboardControlMessage1_15.serialize(message);
|
||||
}
|
||||
|
||||
public createScrollController(): ScrcpyScrollController {
|
||||
createScrollController(): ScrcpyScrollController {
|
||||
return new ScrcpyScrollController1_16();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,31 +24,31 @@ export const ScrcpyInjectScrollControlMessage1_16 = new Struct()
|
|||
* reaches 1 or -1.
|
||||
*/
|
||||
export class ScrcpyScrollController1_16 implements ScrcpyScrollController {
|
||||
private accumulatedX = 0;
|
||||
private accumulatedY = 0;
|
||||
#accumulatedX = 0;
|
||||
#accumulatedY = 0;
|
||||
|
||||
protected processMessage(
|
||||
message: ScrcpyInjectScrollControlMessage,
|
||||
): ScrcpyInjectScrollControlMessage | undefined {
|
||||
this.accumulatedX += message.scrollX;
|
||||
this.accumulatedY += message.scrollY;
|
||||
this.#accumulatedX += message.scrollX;
|
||||
this.#accumulatedY += message.scrollY;
|
||||
|
||||
let scrollX = 0;
|
||||
let scrollY = 0;
|
||||
if (this.accumulatedX >= 1) {
|
||||
if (this.#accumulatedX >= 1) {
|
||||
scrollX = 1;
|
||||
this.accumulatedX = 0;
|
||||
} else if (this.accumulatedX <= -1) {
|
||||
this.#accumulatedX = 0;
|
||||
} else if (this.#accumulatedX <= -1) {
|
||||
scrollX = -1;
|
||||
this.accumulatedX = 0;
|
||||
this.#accumulatedX = 0;
|
||||
}
|
||||
|
||||
if (this.accumulatedY >= 1) {
|
||||
if (this.#accumulatedY >= 1) {
|
||||
scrollY = 1;
|
||||
this.accumulatedY = 0;
|
||||
} else if (this.accumulatedY <= -1) {
|
||||
this.#accumulatedY = 0;
|
||||
} else if (this.#accumulatedY <= -1) {
|
||||
scrollY = -1;
|
||||
this.accumulatedY = 0;
|
||||
this.#accumulatedY = 0;
|
||||
}
|
||||
|
||||
if (scrollX === 0 && scrollY === 0) {
|
||||
|
@ -60,7 +60,7 @@ export class ScrcpyScrollController1_16 implements ScrcpyScrollController {
|
|||
return message;
|
||||
}
|
||||
|
||||
public serializeScrollMessage(
|
||||
serializeScrollMessage(
|
||||
message: ScrcpyInjectScrollControlMessage,
|
||||
): Uint8Array | undefined {
|
||||
const processed = this.processMessage(message);
|
||||
|
|
|
@ -11,17 +11,17 @@ export class ScrcpyOptions1_17 extends ScrcpyOptionsBase<
|
|||
ScrcpyOptionsInit1_17,
|
||||
ScrcpyOptions1_16
|
||||
> {
|
||||
public static readonly DEFAULTS = {
|
||||
static readonly DEFAULTS = {
|
||||
...ScrcpyOptions1_16.DEFAULTS,
|
||||
encoderName: undefined,
|
||||
} as const satisfies Required<ScrcpyOptionsInit1_17>;
|
||||
|
||||
public static readonly SERIALIZE_ORDER = [
|
||||
static readonly SERIALIZE_ORDER = [
|
||||
...ScrcpyOptions1_16.SERIALIZE_ORDER,
|
||||
"encoderName",
|
||||
] as const satisfies readonly (keyof ScrcpyOptionsInit1_17)[];
|
||||
|
||||
public static parseEncoder(
|
||||
static parseEncoder(
|
||||
line: string,
|
||||
encoderNameRegex: RegExp,
|
||||
): ScrcpyEncoder | undefined {
|
||||
|
@ -32,32 +32,32 @@ export class ScrcpyOptions1_17 extends ScrcpyOptionsBase<
|
|||
return undefined;
|
||||
}
|
||||
|
||||
public override get defaults(): Required<ScrcpyOptionsInit1_17> {
|
||||
override get defaults(): Required<ScrcpyOptionsInit1_17> {
|
||||
return ScrcpyOptions1_17.DEFAULTS;
|
||||
}
|
||||
|
||||
public constructor(init: ScrcpyOptionsInit1_17) {
|
||||
constructor(init: ScrcpyOptionsInit1_17) {
|
||||
super(new ScrcpyOptions1_16(init), {
|
||||
...ScrcpyOptions1_17.DEFAULTS,
|
||||
...init,
|
||||
});
|
||||
}
|
||||
|
||||
public override serialize(): string[] {
|
||||
override serialize(): string[] {
|
||||
return ScrcpyOptions1_16.serialize(
|
||||
this.value,
|
||||
ScrcpyOptions1_17.SERIALIZE_ORDER,
|
||||
);
|
||||
}
|
||||
|
||||
public override setListEncoders() {
|
||||
override setListEncoders() {
|
||||
// Set to an invalid value
|
||||
// Server will print valid values before crashing
|
||||
// (server will crash after opening video and control sockets)
|
||||
this.value.encoderName = "_";
|
||||
}
|
||||
|
||||
public override parseEncoder(line: string): ScrcpyEncoder | undefined {
|
||||
override parseEncoder(line: string): ScrcpyEncoder | undefined {
|
||||
return ScrcpyOptions1_17.parseEncoder(
|
||||
line,
|
||||
/\s+scrcpy --encoder-name '(.*?)'/,
|
||||
|
|
|
@ -82,23 +82,23 @@ export class ScrcpyOptions1_18 extends ScrcpyOptionsBase<
|
|||
ScrcpyOptionsInit1_18,
|
||||
ScrcpyOptions1_17
|
||||
> {
|
||||
public static readonly DEFAULTS = {
|
||||
static readonly DEFAULTS = {
|
||||
...ScrcpyOptions1_17.DEFAULTS,
|
||||
logLevel: ScrcpyLogLevel1_18.Debug,
|
||||
lockVideoOrientation: ScrcpyVideoOrientation1_18.Unlocked,
|
||||
powerOffOnClose: false,
|
||||
} as const satisfies Required<ScrcpyOptionsInit1_18>;
|
||||
|
||||
public static readonly SERIALIZE_ORDER = [
|
||||
static readonly SERIALIZE_ORDER = [
|
||||
...ScrcpyOptions1_17.SERIALIZE_ORDER,
|
||||
"powerOffOnClose",
|
||||
] as const satisfies readonly (keyof ScrcpyOptionsInit1_18)[];
|
||||
|
||||
public override get defaults(): Required<ScrcpyOptionsInit1_18> {
|
||||
override get defaults(): Required<ScrcpyOptionsInit1_18> {
|
||||
return ScrcpyOptions1_18.DEFAULTS;
|
||||
}
|
||||
|
||||
public override get controlMessageTypes() {
|
||||
override get controlMessageTypes() {
|
||||
return SCRCPY_CONTROL_MESSAGE_TYPES_1_18;
|
||||
}
|
||||
|
||||
|
@ -115,21 +115,21 @@ export class ScrcpyOptions1_18 extends ScrcpyOptionsBase<
|
|||
);
|
||||
}
|
||||
|
||||
public override serialize(): string[] {
|
||||
override serialize(): string[] {
|
||||
return ScrcpyOptions1_16.serialize(
|
||||
this.value,
|
||||
ScrcpyOptions1_18.SERIALIZE_ORDER,
|
||||
);
|
||||
}
|
||||
|
||||
public override parseEncoder(line: string): ScrcpyEncoder | undefined {
|
||||
override parseEncoder(line: string): ScrcpyEncoder | undefined {
|
||||
return ScrcpyOptions1_17.parseEncoder(
|
||||
line,
|
||||
/\s+scrcpy --encoder '(.*?)'/,
|
||||
);
|
||||
}
|
||||
|
||||
public override serializeBackOrScreenOnControlMessage(
|
||||
override serializeBackOrScreenOnControlMessage(
|
||||
message: ScrcpyBackOrScreenOnControlMessage,
|
||||
) {
|
||||
return ScrcpyBackOrScreenOnControlMessage1_18.serialize(message);
|
||||
|
|
|
@ -30,12 +30,12 @@ export class ScrcpyOptions1_21 extends ScrcpyOptionsBase<
|
|||
ScrcpyOptionsInit1_21,
|
||||
ScrcpyOptions1_18
|
||||
> {
|
||||
public static readonly DEFAULTS = {
|
||||
static readonly DEFAULTS = {
|
||||
...ScrcpyOptions1_18.DEFAULTS,
|
||||
clipboardAutosync: true,
|
||||
} as const satisfies Required<ScrcpyOptionsInit1_21>;
|
||||
|
||||
public static serialize<T extends object>(
|
||||
static serialize<T extends object>(
|
||||
options: T,
|
||||
defaults: Required<T>,
|
||||
): string[] {
|
||||
|
@ -60,22 +60,22 @@ export class ScrcpyOptions1_21 extends ScrcpyOptionsBase<
|
|||
return result;
|
||||
}
|
||||
|
||||
public override get defaults(): Required<ScrcpyOptionsInit1_21> {
|
||||
override get defaults(): Required<ScrcpyOptionsInit1_21> {
|
||||
return ScrcpyOptions1_21.DEFAULTS;
|
||||
}
|
||||
|
||||
public constructor(init: ScrcpyOptionsInit1_21) {
|
||||
constructor(init: ScrcpyOptionsInit1_21) {
|
||||
super(new ScrcpyOptions1_18(init), {
|
||||
...ScrcpyOptions1_21.DEFAULTS,
|
||||
...init,
|
||||
});
|
||||
}
|
||||
|
||||
public override serialize(): string[] {
|
||||
override serialize(): string[] {
|
||||
return ScrcpyOptions1_21.serialize(this.value, this.defaults);
|
||||
}
|
||||
|
||||
public override serializeSetClipboardControlMessage(
|
||||
override serializeSetClipboardControlMessage(
|
||||
message: ScrcpySetClipboardControlMessage,
|
||||
): Uint8Array {
|
||||
return ScrcpySetClipboardControlMessage1_21.serialize(message);
|
||||
|
|
|
@ -14,25 +14,25 @@ export class ScrcpyOptions1_22 extends ScrcpyOptionsBase<
|
|||
ScrcpyOptionsInit1_22,
|
||||
ScrcpyOptions1_21
|
||||
> {
|
||||
public static readonly DEFAULTS = {
|
||||
static readonly DEFAULTS = {
|
||||
...ScrcpyOptions1_21.DEFAULTS,
|
||||
downsizeOnError: true,
|
||||
sendDeviceMeta: true,
|
||||
sendDummyByte: true,
|
||||
} as const satisfies Required<ScrcpyOptionsInit1_22>;
|
||||
|
||||
public override get defaults(): Required<ScrcpyOptionsInit1_22> {
|
||||
override get defaults(): Required<ScrcpyOptionsInit1_22> {
|
||||
return ScrcpyOptions1_22.DEFAULTS;
|
||||
}
|
||||
|
||||
public constructor(init: ScrcpyOptionsInit1_22) {
|
||||
constructor(init: ScrcpyOptionsInit1_22) {
|
||||
super(new ScrcpyOptions1_21(init), {
|
||||
...ScrcpyOptions1_22.DEFAULTS,
|
||||
...init,
|
||||
});
|
||||
}
|
||||
|
||||
public override parseVideoStreamMetadata(
|
||||
override parseVideoStreamMetadata(
|
||||
stream: ReadableStream<Uint8Array>,
|
||||
): ValueOrPromise<ScrcpyVideoStream> {
|
||||
if (!this.value.sendDeviceMeta) {
|
||||
|
@ -42,11 +42,11 @@ export class ScrcpyOptions1_22 extends ScrcpyOptionsBase<
|
|||
}
|
||||
}
|
||||
|
||||
public override serialize(): string[] {
|
||||
override serialize(): string[] {
|
||||
return ScrcpyOptions1_21.serialize(this.value, this.defaults);
|
||||
}
|
||||
|
||||
public override createScrollController(): ScrcpyScrollController {
|
||||
override createScrollController(): ScrcpyScrollController {
|
||||
return new ScrcpyScrollController1_22();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ export type ScrcpyInjectScrollControlMessage1_22 =
|
|||
(typeof ScrcpyInjectScrollControlMessage1_22)["TInit"];
|
||||
|
||||
export class ScrcpyScrollController1_22 extends ScrcpyScrollController1_16 {
|
||||
public override serializeScrollMessage(
|
||||
override serializeScrollMessage(
|
||||
message: ScrcpyInjectScrollControlMessage1_22,
|
||||
): Uint8Array | undefined {
|
||||
const processed = this.processMessage(message);
|
||||
|
|
|
@ -16,27 +16,27 @@ export class ScrcpyOptions1_23 extends ScrcpyOptionsBase<
|
|||
ScrcpyOptionsInit1_23,
|
||||
ScrcpyOptions1_22
|
||||
> {
|
||||
public static readonly DEFAULTS = {
|
||||
static readonly DEFAULTS = {
|
||||
...ScrcpyOptions1_22.DEFAULTS,
|
||||
cleanup: true,
|
||||
} as const satisfies Required<ScrcpyOptionsInit1_23>;
|
||||
|
||||
public override get defaults(): Required<ScrcpyOptionsInit1_23> {
|
||||
override get defaults(): Required<ScrcpyOptionsInit1_23> {
|
||||
return ScrcpyOptions1_23.DEFAULTS;
|
||||
}
|
||||
|
||||
public constructor(init: ScrcpyOptionsInit1_22) {
|
||||
constructor(init: ScrcpyOptionsInit1_22) {
|
||||
super(new ScrcpyOptions1_22(init), {
|
||||
...ScrcpyOptions1_23.DEFAULTS,
|
||||
...init,
|
||||
});
|
||||
}
|
||||
|
||||
public override serialize(): string[] {
|
||||
override serialize(): string[] {
|
||||
return ScrcpyOptions1_21.serialize(this.value, this.defaults);
|
||||
}
|
||||
|
||||
public override createMediaStreamTransformer(): TransformStream<
|
||||
override createMediaStreamTransformer(): TransformStream<
|
||||
Uint8Array,
|
||||
ScrcpyMediaStreamPacket
|
||||
> {
|
||||
|
|
|
@ -11,23 +11,23 @@ export class ScrcpyOptions1_24 extends ScrcpyOptionsBase<
|
|||
ScrcpyOptionsInit1_24,
|
||||
ScrcpyOptions1_23
|
||||
> {
|
||||
public static readonly DEFAULTS = {
|
||||
static readonly DEFAULTS = {
|
||||
...ScrcpyOptions1_23.DEFAULTS,
|
||||
powerOn: true,
|
||||
} as const satisfies Required<ScrcpyOptionsInit1_24>;
|
||||
|
||||
public override get defaults(): Required<ScrcpyOptionsInit1_24> {
|
||||
override get defaults(): Required<ScrcpyOptionsInit1_24> {
|
||||
return ScrcpyOptions1_24.DEFAULTS;
|
||||
}
|
||||
|
||||
public constructor(init: ScrcpyOptionsInit1_24) {
|
||||
constructor(init: ScrcpyOptionsInit1_24) {
|
||||
super(new ScrcpyOptions1_23(init), {
|
||||
...ScrcpyOptions1_24.DEFAULTS,
|
||||
...init,
|
||||
});
|
||||
}
|
||||
|
||||
public override serialize(): string[] {
|
||||
override serialize(): string[] {
|
||||
return ScrcpyOptions1_21.serialize(this.value, this.defaults);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,22 +10,22 @@ export class ScrcpyOptions1_25 extends ScrcpyOptionsBase<
|
|||
ScrcpyOptionsInit1_24,
|
||||
ScrcpyOptions1_24
|
||||
> {
|
||||
public override get defaults(): Required<ScrcpyOptionsInit1_24> {
|
||||
override get defaults(): Required<ScrcpyOptionsInit1_24> {
|
||||
return ScrcpyOptions1_24.DEFAULTS;
|
||||
}
|
||||
|
||||
public constructor(init: ScrcpyOptionsInit1_24) {
|
||||
constructor(init: ScrcpyOptionsInit1_24) {
|
||||
super(new ScrcpyOptions1_24(init), {
|
||||
...ScrcpyOptions1_24.DEFAULTS,
|
||||
...init,
|
||||
});
|
||||
}
|
||||
|
||||
public override serialize(): string[] {
|
||||
override serialize(): string[] {
|
||||
return ScrcpyOptions1_21.serialize(this.value, this.defaults);
|
||||
}
|
||||
|
||||
public override createScrollController(): ScrcpyScrollController {
|
||||
override createScrollController(): ScrcpyScrollController {
|
||||
return new ScrcpyScrollController1_25();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,20 +45,20 @@ export type ScrcpyInjectTouchControlMessage2_0 =
|
|||
(typeof ScrcpyInjectTouchControlMessage2_0)["TInit"];
|
||||
|
||||
export class ScrcpyInstanceId implements ScrcpyOptionValue {
|
||||
public static readonly NONE = new ScrcpyInstanceId(-1);
|
||||
static readonly NONE = new ScrcpyInstanceId(-1);
|
||||
|
||||
public static random(): ScrcpyInstanceId {
|
||||
static random(): ScrcpyInstanceId {
|
||||
// A random 31-bit unsigned integer
|
||||
return new ScrcpyInstanceId((Math.random() * 0x80000000) | 0);
|
||||
}
|
||||
|
||||
public value: number;
|
||||
value: number;
|
||||
|
||||
public constructor(value: number) {
|
||||
constructor(value: number) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public toOptionValue(): string | undefined {
|
||||
toOptionValue(): string | undefined {
|
||||
if (this.value < 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
@ -106,7 +106,7 @@ export class ScrcpyOptions2_0 extends ScrcpyOptionsBase<
|
|||
ScrcpyOptionsInit2_0,
|
||||
ScrcpyOptions1_25
|
||||
> {
|
||||
public static readonly DEFAULTS = {
|
||||
static readonly DEFAULTS = {
|
||||
...omit(ScrcpyOptions1_24.DEFAULTS, [
|
||||
"bitRate",
|
||||
"codecOptions",
|
||||
|
@ -130,30 +130,30 @@ export class ScrcpyOptions2_0 extends ScrcpyOptionsBase<
|
|||
sendCodecMeta: true,
|
||||
} as const satisfies Required<ScrcpyOptionsInit2_0>;
|
||||
|
||||
public override get defaults(): Required<ScrcpyOptionsInit2_0> {
|
||||
override get defaults(): Required<ScrcpyOptionsInit2_0> {
|
||||
return ScrcpyOptions2_0.DEFAULTS;
|
||||
}
|
||||
|
||||
public constructor(init: ScrcpyOptionsInit2_0) {
|
||||
constructor(init: ScrcpyOptionsInit2_0) {
|
||||
super(new ScrcpyOptions1_25(init), {
|
||||
...ScrcpyOptions2_0.DEFAULTS,
|
||||
...init,
|
||||
});
|
||||
}
|
||||
|
||||
public override serialize(): string[] {
|
||||
override serialize(): string[] {
|
||||
return ScrcpyOptions1_21.serialize(this.value, this.defaults);
|
||||
}
|
||||
|
||||
public override setListEncoders(): void {
|
||||
override setListEncoders(): void {
|
||||
this.value.listEncoders = true;
|
||||
}
|
||||
|
||||
public override setListDisplays(): void {
|
||||
override setListDisplays(): void {
|
||||
this.value.listDisplays = true;
|
||||
}
|
||||
|
||||
public override parseEncoder(line: string): ScrcpyEncoder | undefined {
|
||||
override parseEncoder(line: string): ScrcpyEncoder | undefined {
|
||||
let match = line.match(
|
||||
/\s+--video-codec=(.*)\s+--video-encoder='(.*)'/,
|
||||
);
|
||||
|
@ -177,7 +177,7 @@ export class ScrcpyOptions2_0 extends ScrcpyOptionsBase<
|
|||
return undefined;
|
||||
}
|
||||
|
||||
public override parseDisplay(line: string): ScrcpyDisplay | undefined {
|
||||
override parseDisplay(line: string): ScrcpyDisplay | undefined {
|
||||
const match = line.match(/\s+--display=(\d+)\s+\((.*?)\)/);
|
||||
if (match) {
|
||||
const display: ScrcpyDisplay = {
|
||||
|
@ -191,7 +191,7 @@ export class ScrcpyOptions2_0 extends ScrcpyOptionsBase<
|
|||
return undefined;
|
||||
}
|
||||
|
||||
public override parseVideoStreamMetadata(
|
||||
override parseVideoStreamMetadata(
|
||||
stream: ReadableStream<Uint8Array>,
|
||||
): ValueOrPromise<ScrcpyVideoStream> {
|
||||
const { sendDeviceMeta, sendCodecMeta } = this.value;
|
||||
|
@ -249,7 +249,7 @@ export class ScrcpyOptions2_0 extends ScrcpyOptionsBase<
|
|||
})();
|
||||
}
|
||||
|
||||
public override parseAudioStreamMetadata(
|
||||
override parseAudioStreamMetadata(
|
||||
stream: ReadableStream<Uint8Array>,
|
||||
): ValueOrPromise<ScrcpyAudioStreamMetadata> {
|
||||
return (async (): Promise<ScrcpyAudioStreamMetadata> => {
|
||||
|
@ -334,7 +334,7 @@ export class ScrcpyOptions2_0 extends ScrcpyOptionsBase<
|
|||
})();
|
||||
}
|
||||
|
||||
public override serializeInjectTouchControlMessage(
|
||||
override serializeInjectTouchControlMessage(
|
||||
message: ScrcpyInjectTouchControlMessage,
|
||||
): Uint8Array {
|
||||
return ScrcpyInjectTouchControlMessage2_0.serialize(message);
|
||||
|
|
|
@ -12,24 +12,24 @@ export class ScrcpyOptions2_1 extends ScrcpyOptionsBase<
|
|||
ScrcpyOptionsInit2_1,
|
||||
ScrcpyOptions2_0
|
||||
> {
|
||||
public static readonly DEFAULTS = {
|
||||
static readonly DEFAULTS = {
|
||||
...ScrcpyOptions2_0.DEFAULTS,
|
||||
video: true,
|
||||
audioSource: "output",
|
||||
} as const satisfies Required<ScrcpyOptionsInit2_1>;
|
||||
|
||||
public override get defaults(): Required<ScrcpyOptionsInit2_1> {
|
||||
override get defaults(): Required<ScrcpyOptionsInit2_1> {
|
||||
return ScrcpyOptions2_1.DEFAULTS;
|
||||
}
|
||||
|
||||
public constructor(init: ScrcpyOptionsInit2_1) {
|
||||
constructor(init: ScrcpyOptionsInit2_1) {
|
||||
super(new ScrcpyOptions2_0(init), {
|
||||
...ScrcpyOptions2_1.DEFAULTS,
|
||||
...init,
|
||||
});
|
||||
}
|
||||
|
||||
public override serialize(): string[] {
|
||||
override serialize(): string[] {
|
||||
return ScrcpyOptions1_21.serialize(this.value, this.defaults);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,31 +21,31 @@ export interface ScrcpyVideoStream {
|
|||
}
|
||||
|
||||
export class ScrcpyAudioCodec implements ScrcpyOptionValue {
|
||||
public static readonly OPUS = new ScrcpyAudioCodec(
|
||||
static readonly OPUS = new ScrcpyAudioCodec(
|
||||
"opus",
|
||||
0x6f_70_75_73,
|
||||
"audio/opus",
|
||||
"opus",
|
||||
);
|
||||
public static readonly AAC = new ScrcpyAudioCodec(
|
||||
static readonly AAC = new ScrcpyAudioCodec(
|
||||
"aac",
|
||||
0x00_61_61_63,
|
||||
"audio/aac",
|
||||
"mp4a.66",
|
||||
);
|
||||
public static readonly RAW = new ScrcpyAudioCodec(
|
||||
static readonly RAW = new ScrcpyAudioCodec(
|
||||
"raw",
|
||||
0x00_72_61_77,
|
||||
"audio/raw",
|
||||
"1",
|
||||
);
|
||||
|
||||
public readonly optionValue: string;
|
||||
public readonly metadataValue: number;
|
||||
public readonly mimeType: string;
|
||||
public readonly webCodecId: string;
|
||||
readonly optionValue: string;
|
||||
readonly metadataValue: number;
|
||||
readonly mimeType: string;
|
||||
readonly webCodecId: string;
|
||||
|
||||
public constructor(
|
||||
constructor(
|
||||
optionValue: string,
|
||||
metadataValue: number,
|
||||
mimeType: string,
|
||||
|
@ -57,7 +57,7 @@ export class ScrcpyAudioCodec implements ScrcpyOptionValue {
|
|||
this.webCodecId = webCodecId;
|
||||
}
|
||||
|
||||
public toOptionValue(): string {
|
||||
toOptionValue(): string {
|
||||
return this.optionValue;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -130,34 +130,34 @@ export abstract class ScrcpyOptionsBase<
|
|||
{
|
||||
protected _base: B;
|
||||
|
||||
public abstract get defaults(): Required<T>;
|
||||
abstract get defaults(): Required<T>;
|
||||
|
||||
public get controlMessageTypes(): readonly ScrcpyControlMessageType[] {
|
||||
get controlMessageTypes(): readonly ScrcpyControlMessageType[] {
|
||||
return this._base.controlMessageTypes;
|
||||
}
|
||||
|
||||
public readonly value: Required<T>;
|
||||
readonly value: Required<T>;
|
||||
|
||||
public constructor(base: B, value: Required<T>) {
|
||||
constructor(base: B, value: Required<T>) {
|
||||
this._base = base;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public abstract serialize(): string[];
|
||||
abstract serialize(): string[];
|
||||
|
||||
public setListEncoders(): void {
|
||||
setListEncoders(): void {
|
||||
this._base.setListEncoders();
|
||||
}
|
||||
|
||||
public setListDisplays(): void {
|
||||
setListDisplays(): void {
|
||||
this._base.setListDisplays();
|
||||
}
|
||||
|
||||
public parseEncoder(line: string): ScrcpyEncoder | undefined {
|
||||
parseEncoder(line: string): ScrcpyEncoder | undefined {
|
||||
return this._base.parseEncoder(line);
|
||||
}
|
||||
|
||||
public parseDisplay(line: string): ScrcpyDisplay | undefined {
|
||||
parseDisplay(line: string): ScrcpyDisplay | undefined {
|
||||
return this._base.parseDisplay(line);
|
||||
}
|
||||
|
||||
|
@ -169,44 +169,44 @@ export abstract class ScrcpyOptionsBase<
|
|||
*
|
||||
* The returned video stream may be different from the input stream, and should be used for further processing.
|
||||
*/
|
||||
public parseVideoStreamMetadata(
|
||||
parseVideoStreamMetadata(
|
||||
stream: ReadableStream<Uint8Array>,
|
||||
): ValueOrPromise<ScrcpyVideoStream> {
|
||||
return this._base.parseVideoStreamMetadata(stream);
|
||||
}
|
||||
|
||||
public parseAudioStreamMetadata(
|
||||
parseAudioStreamMetadata(
|
||||
stream: ReadableStream<Uint8Array>,
|
||||
): ValueOrPromise<ScrcpyAudioStreamMetadata> {
|
||||
return this._base.parseAudioStreamMetadata(stream);
|
||||
}
|
||||
|
||||
public createMediaStreamTransformer(): TransformStream<
|
||||
createMediaStreamTransformer(): TransformStream<
|
||||
Uint8Array,
|
||||
ScrcpyMediaStreamPacket
|
||||
> {
|
||||
return this._base.createMediaStreamTransformer();
|
||||
}
|
||||
|
||||
public serializeInjectTouchControlMessage(
|
||||
serializeInjectTouchControlMessage(
|
||||
message: ScrcpyInjectTouchControlMessage,
|
||||
): Uint8Array {
|
||||
return this._base.serializeInjectTouchControlMessage(message);
|
||||
}
|
||||
|
||||
public serializeBackOrScreenOnControlMessage(
|
||||
serializeBackOrScreenOnControlMessage(
|
||||
message: ScrcpyBackOrScreenOnControlMessage,
|
||||
): Uint8Array | undefined {
|
||||
return this._base.serializeBackOrScreenOnControlMessage(message);
|
||||
}
|
||||
|
||||
public serializeSetClipboardControlMessage(
|
||||
serializeSetClipboardControlMessage(
|
||||
message: ScrcpySetClipboardControlMessage,
|
||||
): Uint8Array {
|
||||
return this._base.serializeSetClipboardControlMessage(message);
|
||||
}
|
||||
|
||||
public createScrollController(): ScrcpyScrollController {
|
||||
createScrollController(): ScrcpyScrollController {
|
||||
return this._base.createScrollController();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,12 +12,12 @@ export class BufferedTransformStream<T>
|
|||
implements ReadableWritablePair<T, Uint8Array>
|
||||
{
|
||||
#readable: ReadableStream<T>;
|
||||
public get readable() {
|
||||
get readable() {
|
||||
return this.#readable;
|
||||
}
|
||||
|
||||
#writable: WritableStream<Uint8Array>;
|
||||
public get writable() {
|
||||
get writable() {
|
||||
return this.#writable;
|
||||
}
|
||||
|
||||
|
|
|
@ -14,19 +14,19 @@ export class BufferedReadableStream implements AsyncExactReadable {
|
|||
#bufferedLength = 0;
|
||||
|
||||
#position = 0;
|
||||
public get position() {
|
||||
get position() {
|
||||
return this.#position;
|
||||
}
|
||||
|
||||
protected readonly stream: ReadableStream<Uint8Array>;
|
||||
protected readonly reader: ReadableStreamDefaultReader<Uint8Array>;
|
||||
|
||||
public constructor(stream: ReadableStream<Uint8Array>) {
|
||||
constructor(stream: ReadableStream<Uint8Array>) {
|
||||
this.stream = stream;
|
||||
this.reader = stream.getReader();
|
||||
}
|
||||
|
||||
private async readSource() {
|
||||
async #readSource() {
|
||||
const { done, value } = await this.reader.read();
|
||||
if (done) {
|
||||
throw new ExactReadableEndedError();
|
||||
|
@ -35,7 +35,7 @@ export class BufferedReadableStream implements AsyncExactReadable {
|
|||
return value;
|
||||
}
|
||||
|
||||
private async readAsync(length: number, initial?: Uint8Array) {
|
||||
async #readAsync(length: number, initial?: Uint8Array) {
|
||||
let result: Uint8Array;
|
||||
let index: number;
|
||||
|
||||
|
@ -45,7 +45,7 @@ export class BufferedReadableStream implements AsyncExactReadable {
|
|||
index = initial.byteLength;
|
||||
length -= initial.byteLength;
|
||||
} else {
|
||||
const array = await this.readSource();
|
||||
const array = await this.#readSource();
|
||||
if (array.byteLength === length) {
|
||||
return array;
|
||||
}
|
||||
|
@ -64,7 +64,7 @@ export class BufferedReadableStream implements AsyncExactReadable {
|
|||
}
|
||||
|
||||
while (length > 0) {
|
||||
const array = await this.readSource();
|
||||
const array = await this.#readSource();
|
||||
if (array.byteLength === length) {
|
||||
result.set(array, index);
|
||||
return result;
|
||||
|
@ -91,7 +91,7 @@ export class BufferedReadableStream implements AsyncExactReadable {
|
|||
* @param length
|
||||
* @returns
|
||||
*/
|
||||
public readExactly(length: number): Uint8Array | Promise<Uint8Array> {
|
||||
readExactly(length: number): Uint8Array | Promise<Uint8Array> {
|
||||
// PERF: Add a synchronous path for reading from internal buffer
|
||||
if (this.#buffered) {
|
||||
const array = this.#buffered;
|
||||
|
@ -107,10 +107,10 @@ export class BufferedReadableStream implements AsyncExactReadable {
|
|||
this.#buffered = undefined;
|
||||
this.#bufferedLength = 0;
|
||||
this.#bufferedOffset = 0;
|
||||
return this.readAsync(length, array.subarray(offset));
|
||||
return this.#readAsync(length, array.subarray(offset));
|
||||
}
|
||||
|
||||
return this.readAsync(length);
|
||||
return this.#readAsync(length);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -118,7 +118,7 @@ export class BufferedReadableStream implements AsyncExactReadable {
|
|||
* all data from the wrapped stream.
|
||||
* @returns A `ReadableStream`
|
||||
*/
|
||||
public release(): ReadableStream<Uint8Array> {
|
||||
release(): ReadableStream<Uint8Array> {
|
||||
if (this.#bufferedLength > 0) {
|
||||
return new PushReadableStream<Uint8Array>(async (controller) => {
|
||||
// Put the remaining data back to the stream
|
||||
|
@ -147,7 +147,7 @@ export class BufferedReadableStream implements AsyncExactReadable {
|
|||
}
|
||||
}
|
||||
|
||||
public cancel(reason?: unknown) {
|
||||
cancel(reason?: unknown) {
|
||||
return this.reader.cancel(reason);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,7 +41,7 @@ export class ConcatStringStream {
|
|||
this.#readableController.error(reason);
|
||||
},
|
||||
});
|
||||
public get writable(): WritableStream<string> {
|
||||
get writable(): WritableStream<string> {
|
||||
return this.#writable;
|
||||
}
|
||||
|
||||
|
@ -51,11 +51,11 @@ export class ConcatStringStream {
|
|||
this.#readableController = controller;
|
||||
},
|
||||
}) as ConcatStringReadableStream;
|
||||
public get readable(): ConcatStringReadableStream {
|
||||
get readable(): ConcatStringReadableStream {
|
||||
return this.#readable;
|
||||
}
|
||||
|
||||
public constructor() {
|
||||
constructor() {
|
||||
void Object.defineProperties(this.#readable, {
|
||||
then: {
|
||||
get: () =>
|
||||
|
@ -127,7 +127,7 @@ export class ConcatBufferStream {
|
|||
this.#readableController.error(reason);
|
||||
},
|
||||
});
|
||||
public get writable(): WritableStream<Uint8Array> {
|
||||
get writable(): WritableStream<Uint8Array> {
|
||||
return this.#writable;
|
||||
}
|
||||
|
||||
|
@ -137,11 +137,11 @@ export class ConcatBufferStream {
|
|||
this.#readableController = controller;
|
||||
},
|
||||
}) as ConcatBufferReadableStream;
|
||||
public get readable(): ConcatBufferReadableStream {
|
||||
get readable(): ConcatBufferReadableStream {
|
||||
return this.#readable;
|
||||
}
|
||||
|
||||
public constructor() {
|
||||
constructor() {
|
||||
void Object.defineProperties(this.#readable, {
|
||||
then: {
|
||||
get: () =>
|
||||
|
|
|
@ -29,25 +29,25 @@ export class Consumable<T> {
|
|||
readonly #task: Task;
|
||||
readonly #resolver: PromiseResolver<void>;
|
||||
|
||||
public readonly value: T;
|
||||
public readonly consumed: Promise<void>;
|
||||
readonly value: T;
|
||||
readonly consumed: Promise<void>;
|
||||
|
||||
public constructor(value: T) {
|
||||
constructor(value: T) {
|
||||
this.#task = createTask("Consumable");
|
||||
this.value = value;
|
||||
this.#resolver = new PromiseResolver<void>();
|
||||
this.consumed = this.#resolver.promise;
|
||||
}
|
||||
|
||||
public consume() {
|
||||
consume() {
|
||||
this.#resolver.resolve();
|
||||
}
|
||||
|
||||
public error(error: any) {
|
||||
error(error: any) {
|
||||
this.#resolver.reject(error);
|
||||
}
|
||||
|
||||
public async tryConsume<U>(callback: (value: T) => U) {
|
||||
async tryConsume<U>(callback: (value: T) => U) {
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/await-thenable
|
||||
const result = await this.#task.run(() => callback(this.value));
|
||||
|
@ -70,7 +70,7 @@ async function enqueue<T>(
|
|||
}
|
||||
|
||||
export class WrapConsumableStream<T> extends TransformStream<T, Consumable<T>> {
|
||||
public constructor() {
|
||||
constructor() {
|
||||
super({
|
||||
async transform(chunk, controller) {
|
||||
await enqueue(controller, chunk);
|
||||
|
@ -83,7 +83,7 @@ export class UnwrapConsumableStream<T> extends TransformStream<
|
|||
Consumable<T>,
|
||||
T
|
||||
> {
|
||||
public constructor() {
|
||||
constructor() {
|
||||
super({
|
||||
transform(chunk, controller) {
|
||||
controller.enqueue(chunk.value);
|
||||
|
@ -110,7 +110,7 @@ export interface ConsumableReadableStreamSource<T> {
|
|||
}
|
||||
|
||||
export class ConsumableReadableStream<T> extends ReadableStream<Consumable<T>> {
|
||||
public constructor(
|
||||
constructor(
|
||||
source: ConsumableReadableStreamSource<T>,
|
||||
strategy?: QueuingStrategy<T>,
|
||||
) {
|
||||
|
@ -168,7 +168,7 @@ export interface ConsumableWritableStreamSink<T> {
|
|||
}
|
||||
|
||||
export class ConsumableWritableStream<T> extends WritableStream<Consumable<T>> {
|
||||
public static async write<T>(
|
||||
static async write<T>(
|
||||
writer: WritableStreamDefaultWriter<Consumable<T>>,
|
||||
value: T,
|
||||
) {
|
||||
|
@ -177,7 +177,7 @@ export class ConsumableWritableStream<T> extends WritableStream<Consumable<T>> {
|
|||
await consumable.consumed;
|
||||
}
|
||||
|
||||
public constructor(
|
||||
constructor(
|
||||
sink: ConsumableWritableStreamSink<T>,
|
||||
strategy?: QueuingStrategy<T>,
|
||||
) {
|
||||
|
@ -232,7 +232,7 @@ export class ConsumableTransformStream<I, O> extends TransformStream<
|
|||
Consumable<I>,
|
||||
Consumable<O>
|
||||
> {
|
||||
public constructor(transformer: ConsumableTransformer<I, O>) {
|
||||
constructor(transformer: ConsumableTransformer<I, O>) {
|
||||
let wrappedController:
|
||||
| ConsumableReadableStreamController<O>
|
||||
| undefined;
|
||||
|
@ -271,7 +271,7 @@ export class ConsumableInspectStream<T> extends TransformStream<
|
|||
Consumable<T>,
|
||||
Consumable<T>
|
||||
> {
|
||||
public constructor(callback: (value: T) => void) {
|
||||
constructor(callback: (value: T) => void) {
|
||||
super({
|
||||
transform(chunk, controller) {
|
||||
callback(chunk.value);
|
||||
|
|
|
@ -3,7 +3,7 @@ import { decodeUtf8 } from "@yume-chan/struct";
|
|||
import { TransformStream } from "./stream.js";
|
||||
|
||||
export class DecodeUtf8Stream extends TransformStream<Uint8Array, string> {
|
||||
public constructor() {
|
||||
constructor() {
|
||||
super({
|
||||
transform(chunk, controller) {
|
||||
controller.enqueue(decodeUtf8(chunk));
|
||||
|
|
|
@ -9,7 +9,7 @@ export class BufferCombiner {
|
|||
#offset: number;
|
||||
#available: number;
|
||||
|
||||
public constructor(size: number) {
|
||||
constructor(size: number) {
|
||||
this.#capacity = size;
|
||||
this.#buffer = new Uint8Array(size);
|
||||
this.#offset = 0;
|
||||
|
@ -23,7 +23,7 @@ export class BufferCombiner {
|
|||
* A generator that yields buffers of specified size.
|
||||
* It may yield the same buffer multiple times, consume the data before calling `next`.
|
||||
*/
|
||||
public *push(data: Uint8Array): Generator<Uint8Array, void, void> {
|
||||
*push(data: Uint8Array): Generator<Uint8Array, void, void> {
|
||||
let offset = 0;
|
||||
let available = data.byteLength;
|
||||
|
||||
|
@ -65,7 +65,7 @@ export class BufferCombiner {
|
|||
}
|
||||
}
|
||||
|
||||
public flush(): Uint8Array | undefined {
|
||||
flush(): Uint8Array | undefined {
|
||||
if (this.#offset === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
@ -81,7 +81,7 @@ export class DistributionStream extends ConsumableTransformStream<
|
|||
Uint8Array,
|
||||
Uint8Array
|
||||
> {
|
||||
public constructor(size: number, combine = false) {
|
||||
constructor(size: number, combine = false) {
|
||||
const combiner = combine ? new BufferCombiner(size) : undefined;
|
||||
super({
|
||||
async transform(chunk, controller) {
|
||||
|
|
|
@ -50,22 +50,22 @@ export class DuplexStreamFactory<R, W> {
|
|||
#writers: WritableStreamDefaultWriter<W>[] = [];
|
||||
|
||||
#writableClosed = false;
|
||||
public get writableClosed() {
|
||||
get writableClosed() {
|
||||
return this.#writableClosed;
|
||||
}
|
||||
|
||||
#closed = new PromiseResolver<void>();
|
||||
public get closed() {
|
||||
get closed() {
|
||||
return this.#closed.promise;
|
||||
}
|
||||
|
||||
readonly #options: DuplexStreamFactoryOptions;
|
||||
|
||||
public constructor(options?: DuplexStreamFactoryOptions) {
|
||||
constructor(options?: DuplexStreamFactoryOptions) {
|
||||
this.#options = options ?? {};
|
||||
}
|
||||
|
||||
public wrapReadable(readable: ReadableStream<R>): WrapReadableStream<R> {
|
||||
wrapReadable(readable: ReadableStream<R>): WrapReadableStream<R> {
|
||||
return new WrapReadableStream<R>({
|
||||
start: (controller) => {
|
||||
this.#readableControllers.push(controller);
|
||||
|
@ -82,7 +82,7 @@ export class DuplexStreamFactory<R, W> {
|
|||
});
|
||||
}
|
||||
|
||||
public createWritable(stream: WritableStream<W>): WritableStream<W> {
|
||||
createWritable(stream: WritableStream<W>): WritableStream<W> {
|
||||
const writer = stream.getWriter();
|
||||
this.#writers.push(writer);
|
||||
|
||||
|
@ -104,7 +104,7 @@ export class DuplexStreamFactory<R, W> {
|
|||
});
|
||||
}
|
||||
|
||||
public async close() {
|
||||
async close() {
|
||||
if (this.#writableClosed) {
|
||||
return;
|
||||
}
|
||||
|
@ -122,7 +122,7 @@ export class DuplexStreamFactory<R, W> {
|
|||
}
|
||||
}
|
||||
|
||||
public async dispose() {
|
||||
async dispose() {
|
||||
this.#writableClosed = true;
|
||||
this.#closed.resolve();
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@ export class PushReadableStream<T> extends ReadableStream<T> {
|
|||
* when the `Promise` is resolved, and be errored when the `Promise` is rejected.
|
||||
* @param strategy
|
||||
*/
|
||||
public constructor(
|
||||
constructor(
|
||||
source: PushReadableStreamSource<T>,
|
||||
strategy?: QueuingStrategy<T>,
|
||||
) {
|
||||
|
|
|
@ -20,7 +20,7 @@ function* split(
|
|||
}
|
||||
|
||||
export class SplitStringStream extends TransformStream<string, string> {
|
||||
public constructor(separator: string) {
|
||||
constructor(separator: string) {
|
||||
super({
|
||||
transform(chunk, controller) {
|
||||
for (const part of split(chunk, separator)) {
|
||||
|
|
|
@ -6,7 +6,7 @@ import { BufferedTransformStream } from "./buffered-transform.js";
|
|||
export class StructDeserializeStream<
|
||||
T extends Struct<any, any, any, any>,
|
||||
> extends BufferedTransformStream<StructValueType<T>> {
|
||||
public constructor(struct: T) {
|
||||
constructor(struct: T) {
|
||||
super((stream) => {
|
||||
return struct.deserialize(stream);
|
||||
});
|
||||
|
|
|
@ -42,11 +42,11 @@ function getWrappedReadableStream<T>(
|
|||
* 3. Convert native `ReadableStream`s to polyfilled ones so they can `pipe` between.
|
||||
*/
|
||||
export class WrapReadableStream<T> extends ReadableStream<T> {
|
||||
public readable!: ReadableStream<T>;
|
||||
readable!: ReadableStream<T>;
|
||||
|
||||
#reader!: ReadableStreamDefaultReader<T>;
|
||||
|
||||
public constructor(
|
||||
constructor(
|
||||
wrapper:
|
||||
| ReadableStream<T>
|
||||
| WrapReadableStreamStart<T>
|
||||
|
|
|
@ -30,11 +30,11 @@ async function getWrappedWritableStream<T>(
|
|||
}
|
||||
|
||||
export class WrapWritableStream<T> extends WritableStream<T> {
|
||||
public writable!: WritableStream<T>;
|
||||
writable!: WritableStream<T>;
|
||||
|
||||
#writer!: WritableStreamDefaultWriter<T>;
|
||||
|
||||
public constructor(
|
||||
constructor(
|
||||
wrapper:
|
||||
| WritableStream<T>
|
||||
| WrapWritableStreamStart<T>
|
||||
|
@ -73,7 +73,7 @@ export class WrapWritableStream<T> extends WritableStream<T> {
|
|||
});
|
||||
}
|
||||
|
||||
public bePipedThroughFrom<U>(transformer: TransformStream<U, T>) {
|
||||
bePipedThroughFrom<U>(transformer: TransformStream<U, T>) {
|
||||
let promise: Promise<void>;
|
||||
return new WrapWritableStream<U>({
|
||||
start: () => {
|
||||
|
|
|
@ -12,13 +12,13 @@ describe("StructFieldDefinition", () => {
|
|||
describe(".constructor", () => {
|
||||
it("should save the `options` parameter", () => {
|
||||
class MockFieldDefinition extends StructFieldDefinition<number> {
|
||||
public constructor(options: number) {
|
||||
constructor(options: number) {
|
||||
super(options);
|
||||
}
|
||||
public getSize(): number {
|
||||
getSize(): number {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
public create(
|
||||
create(
|
||||
options: Readonly<StructOptions>,
|
||||
struct: StructValue,
|
||||
value: unknown,
|
||||
|
@ -28,17 +28,17 @@ describe("StructFieldDefinition", () => {
|
|||
void value;
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
public override deserialize(
|
||||
override deserialize(
|
||||
options: Readonly<StructOptions>,
|
||||
stream: ExactReadable,
|
||||
struct: StructValue,
|
||||
): StructFieldValue<this>;
|
||||
public override deserialize(
|
||||
override deserialize(
|
||||
options: Readonly<StructOptions>,
|
||||
stream: AsyncExactReadable,
|
||||
struct: StructValue,
|
||||
): Promise<StructFieldValue<this>>;
|
||||
public deserialize(
|
||||
deserialize(
|
||||
options: Readonly<StructOptions>,
|
||||
stream: ExactReadable | AsyncExactReadable,
|
||||
struct: StructValue,
|
||||
|
|
|
@ -19,17 +19,17 @@ export abstract class StructFieldDefinition<
|
|||
* When `T` is a type initiated `StructFieldDefinition`,
|
||||
* use `T['TValue']` to retrieve its `TValue` type parameter.
|
||||
*/
|
||||
public readonly TValue!: TValue;
|
||||
readonly TValue!: TValue;
|
||||
|
||||
/**
|
||||
* When `T` is a type initiated `StructFieldDefinition`,
|
||||
* use `T['TOmitInitKey']` to retrieve its `TOmitInitKey` type parameter.
|
||||
*/
|
||||
public readonly TOmitInitKey!: TOmitInitKey;
|
||||
readonly TOmitInitKey!: TOmitInitKey;
|
||||
|
||||
public readonly options: TOptions;
|
||||
readonly options: TOptions;
|
||||
|
||||
public constructor(options: TOptions) {
|
||||
constructor(options: TOptions) {
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
|
@ -38,12 +38,12 @@ export abstract class StructFieldDefinition<
|
|||
*
|
||||
* Actual size can be retrieved from `StructFieldValue#getSize`
|
||||
*/
|
||||
public abstract getSize(): number;
|
||||
abstract getSize(): number;
|
||||
|
||||
/**
|
||||
* When implemented in derived classes, creates a `StructFieldValue` from a given `value`.
|
||||
*/
|
||||
public abstract create(
|
||||
abstract create(
|
||||
options: Readonly<StructOptions>,
|
||||
structValue: StructValue,
|
||||
value: TValue,
|
||||
|
@ -55,12 +55,12 @@ export abstract class StructFieldDefinition<
|
|||
*
|
||||
* `SyncPromise` can be used to simplify implementation.
|
||||
*/
|
||||
public abstract deserialize(
|
||||
abstract deserialize(
|
||||
options: Readonly<StructOptions>,
|
||||
stream: ExactReadable,
|
||||
structValue: StructValue,
|
||||
): StructFieldValue<this>;
|
||||
public abstract deserialize(
|
||||
abstract deserialize(
|
||||
options: Readonly<StructOptions>,
|
||||
stream: AsyncExactReadable,
|
||||
struct: StructValue,
|
||||
|
|
|
@ -12,7 +12,7 @@ describe("StructFieldValue", () => {
|
|||
describe(".constructor", () => {
|
||||
it("should save parameters", () => {
|
||||
class MockStructFieldValue extends StructFieldValue {
|
||||
public serialize(dataView: DataView, offset: number): void {
|
||||
serialize(dataView: DataView, offset: number): void {
|
||||
void dataView;
|
||||
void offset;
|
||||
throw new Error("Method not implemented.");
|
||||
|
@ -40,10 +40,10 @@ describe("StructFieldValue", () => {
|
|||
describe("#getSize", () => {
|
||||
it("should return same value as definition's", () => {
|
||||
class MockFieldDefinition extends StructFieldDefinition {
|
||||
public getSize(): number {
|
||||
getSize(): number {
|
||||
return 42;
|
||||
}
|
||||
public create(
|
||||
create(
|
||||
options: Readonly<StructOptions>,
|
||||
struct: StructValue,
|
||||
value: unknown,
|
||||
|
@ -54,17 +54,17 @@ describe("StructFieldValue", () => {
|
|||
throw new Error("Method not implemented.");
|
||||
}
|
||||
|
||||
public override deserialize(
|
||||
override deserialize(
|
||||
options: Readonly<StructOptions>,
|
||||
stream: ExactReadable,
|
||||
struct: StructValue,
|
||||
): StructFieldValue<this>;
|
||||
public override deserialize(
|
||||
override deserialize(
|
||||
options: Readonly<StructOptions>,
|
||||
stream: AsyncExactReadable,
|
||||
struct: StructValue,
|
||||
): Promise<StructFieldValue<this>>;
|
||||
public override deserialize(
|
||||
override deserialize(
|
||||
options: Readonly<StructOptions>,
|
||||
stream: ExactReadable | AsyncExactReadable,
|
||||
struct: StructValue,
|
||||
|
@ -77,7 +77,7 @@ describe("StructFieldValue", () => {
|
|||
}
|
||||
|
||||
class MockStructFieldValue extends StructFieldValue {
|
||||
public serialize(dataView: DataView, offset: number): void {
|
||||
serialize(dataView: DataView, offset: number): void {
|
||||
void dataView;
|
||||
void offset;
|
||||
throw new Error("Method not implemented.");
|
||||
|
@ -98,7 +98,7 @@ describe("StructFieldValue", () => {
|
|||
describe("#set", () => {
|
||||
it("should update its internal value", () => {
|
||||
class MockStructFieldValue extends StructFieldValue {
|
||||
public serialize(dataView: DataView, offset: number): void {
|
||||
serialize(dataView: DataView, offset: number): void {
|
||||
void dataView;
|
||||
void offset;
|
||||
throw new Error("Method not implemented.");
|
||||
|
|
|
@ -16,15 +16,15 @@ export abstract class StructFieldValue<
|
|||
> = StructFieldDefinition<any, any, any>,
|
||||
> {
|
||||
/** Gets the definition associated with this runtime value */
|
||||
public readonly definition: TDefinition;
|
||||
readonly definition: TDefinition;
|
||||
|
||||
/** Gets the options of the associated `Struct` */
|
||||
public readonly options: Readonly<StructOptions>;
|
||||
readonly options: Readonly<StructOptions>;
|
||||
|
||||
/** Gets the associated `Struct` instance */
|
||||
public readonly struct: StructValue;
|
||||
readonly struct: StructValue;
|
||||
|
||||
public get hasCustomAccessors(): boolean {
|
||||
get hasCustomAccessors(): boolean {
|
||||
return (
|
||||
this.get !== StructFieldValue.prototype.get ||
|
||||
this.set !== StructFieldValue.prototype.set
|
||||
|
@ -33,7 +33,7 @@ export abstract class StructFieldValue<
|
|||
|
||||
protected value: TDefinition["TValue"];
|
||||
|
||||
public constructor(
|
||||
constructor(
|
||||
definition: TDefinition,
|
||||
options: Readonly<StructOptions>,
|
||||
struct: StructValue,
|
||||
|
@ -50,26 +50,26 @@ export abstract class StructFieldValue<
|
|||
*
|
||||
* When overridden in derived classes, can have custom logic to calculate the actual size.
|
||||
*/
|
||||
public getSize(): number {
|
||||
getSize(): number {
|
||||
return this.definition.getSize();
|
||||
}
|
||||
|
||||
/**
|
||||
* When implemented in derived classes, reads current field's value.
|
||||
*/
|
||||
public get(): TDefinition["TValue"] {
|
||||
get(): TDefinition["TValue"] {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* When implemented in derived classes, updates current field's value.
|
||||
*/
|
||||
public set(value: TDefinition["TValue"]): void {
|
||||
set(value: TDefinition["TValue"]): void {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* When implemented in derived classes, serializes this field into `dataView` at `offset`
|
||||
*/
|
||||
public abstract serialize(dataView: DataView, offset: number): void;
|
||||
abstract serialize(dataView: DataView, offset: number): void;
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ import type { ValueOrPromise } from "../utils.js";
|
|||
// TODO: allow over reading (returning a `Uint8Array`, an `offset` and a `length`) to avoid copying
|
||||
|
||||
export class ExactReadableEndedError extends Error {
|
||||
public constructor() {
|
||||
constructor() {
|
||||
super("ExactReadable ended");
|
||||
Object.setPrototypeOf(this, new.target.prototype);
|
||||
}
|
||||
|
|
|
@ -14,9 +14,9 @@ export class StructValue {
|
|||
/**
|
||||
* Gets the result struct value object
|
||||
*/
|
||||
public readonly value: Record<PropertyKey, unknown>;
|
||||
readonly value: Record<PropertyKey, unknown>;
|
||||
|
||||
public constructor(prototype: object) {
|
||||
constructor(prototype: object) {
|
||||
// PERF: `Object.create(extra)` is 50% faster
|
||||
// than `Object.defineProperties(this.value, extra)`
|
||||
this.value = Object.create(prototype) as Record<PropertyKey, unknown>;
|
||||
|
@ -35,7 +35,7 @@ export class StructValue {
|
|||
* @param name The field name
|
||||
* @param fieldValue The associated `StructFieldValue`
|
||||
*/
|
||||
public set(name: PropertyKey, fieldValue: StructFieldValue): void {
|
||||
set(name: PropertyKey, fieldValue: StructFieldValue): void {
|
||||
this.fieldValues[name] = fieldValue;
|
||||
|
||||
// PERF: `Object.defineProperty` is slow
|
||||
|
@ -61,7 +61,7 @@ export class StructValue {
|
|||
*
|
||||
* @param name The field name
|
||||
*/
|
||||
public get(name: PropertyKey): StructFieldValue {
|
||||
get(name: PropertyKey): StructFieldValue {
|
||||
return this.fieldValues[name]!;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,11 +22,11 @@ import {
|
|||
} from "./index.js";
|
||||
|
||||
class MockDeserializationStream implements ExactReadable {
|
||||
public buffer = new Uint8Array(0);
|
||||
buffer = new Uint8Array(0);
|
||||
|
||||
public position = 0;
|
||||
position = 0;
|
||||
|
||||
public readExactly = jest.fn(() => this.buffer);
|
||||
readExactly = jest.fn(() => this.buffer);
|
||||
}
|
||||
|
||||
describe("Struct", () => {
|
||||
|
@ -40,15 +40,15 @@ describe("Struct", () => {
|
|||
|
||||
describe("#field", () => {
|
||||
class MockFieldDefinition extends StructFieldDefinition<number> {
|
||||
public constructor(size: number) {
|
||||
constructor(size: number) {
|
||||
super(size);
|
||||
}
|
||||
|
||||
public getSize = jest.fn(() => {
|
||||
getSize = jest.fn(() => {
|
||||
return this.options;
|
||||
});
|
||||
|
||||
public create(
|
||||
create(
|
||||
options: Readonly<StructOptions>,
|
||||
struct: StructValue,
|
||||
value: unknown,
|
||||
|
@ -58,17 +58,17 @@ describe("Struct", () => {
|
|||
void value;
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
public override deserialize(
|
||||
override deserialize(
|
||||
options: Readonly<StructOptions>,
|
||||
stream: ExactReadable,
|
||||
struct: StructValue,
|
||||
): StructFieldValue<this>;
|
||||
public override deserialize(
|
||||
override deserialize(
|
||||
options: Readonly<StructOptions>,
|
||||
stream: AsyncExactReadable,
|
||||
struct: StructValue,
|
||||
): Promise<StructFieldValue<this>>;
|
||||
public override deserialize(
|
||||
override deserialize(
|
||||
options: Readonly<StructOptions>,
|
||||
stream: ExactReadable | AsyncExactReadable,
|
||||
struct: StructValue,
|
||||
|
|
|
@ -192,14 +192,14 @@ export type StructDeserializedResult<
|
|||
: TPostDeserialized;
|
||||
|
||||
export class StructDeserializeError extends Error {
|
||||
public constructor(message: string) {
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
Object.setPrototypeOf(this, new.target.prototype);
|
||||
}
|
||||
}
|
||||
|
||||
export class StructNotEnoughDataError extends StructDeserializeError {
|
||||
public constructor() {
|
||||
constructor() {
|
||||
super(
|
||||
"The underlying readable was ended before the struct was fully deserialized",
|
||||
);
|
||||
|
@ -207,7 +207,7 @@ export class StructNotEnoughDataError extends StructDeserializeError {
|
|||
}
|
||||
|
||||
export class StructEmptyError extends StructDeserializeError {
|
||||
public constructor() {
|
||||
constructor() {
|
||||
super("The underlying readable doesn't contain any more struct");
|
||||
}
|
||||
}
|
||||
|
@ -222,27 +222,27 @@ export class Struct<
|
|||
StructDeserializedResult<TFields, TExtra, TPostDeserialized>
|
||||
>
|
||||
{
|
||||
public readonly TFields!: TFields;
|
||||
readonly TFields!: TFields;
|
||||
|
||||
public readonly TOmitInitKey!: TOmitInitKey;
|
||||
readonly TOmitInitKey!: TOmitInitKey;
|
||||
|
||||
public readonly TExtra!: TExtra;
|
||||
readonly TExtra!: TExtra;
|
||||
|
||||
public readonly TInit!: Evaluate<Omit<TFields, TOmitInitKey>>;
|
||||
readonly TInit!: Evaluate<Omit<TFields, TOmitInitKey>>;
|
||||
|
||||
public readonly TDeserializeResult!: StructDeserializedResult<
|
||||
readonly TDeserializeResult!: StructDeserializedResult<
|
||||
TFields,
|
||||
TExtra,
|
||||
TPostDeserialized
|
||||
>;
|
||||
|
||||
public readonly options: Readonly<StructOptions>;
|
||||
readonly options: Readonly<StructOptions>;
|
||||
|
||||
#size = 0;
|
||||
/**
|
||||
* Gets the static size (exclude fields that can change size at runtime)
|
||||
*/
|
||||
public get size() {
|
||||
get size() {
|
||||
return this.#size;
|
||||
}
|
||||
|
||||
|
@ -250,7 +250,7 @@ export class Struct<
|
|||
name: PropertyKey,
|
||||
definition: StructFieldDefinition<any, any, any>,
|
||||
][] = [];
|
||||
public get fields(): readonly [
|
||||
get fields(): readonly [
|
||||
name: PropertyKey,
|
||||
definition: StructFieldDefinition<any, any, any>,
|
||||
][] {
|
||||
|
@ -261,14 +261,14 @@ export class Struct<
|
|||
|
||||
#postDeserialized?: StructPostDeserialized<any, any> | undefined;
|
||||
|
||||
public constructor(options?: Partial<Readonly<StructOptions>>) {
|
||||
constructor(options?: Partial<Readonly<StructOptions>>) {
|
||||
this.options = { ...StructDefaultOptions, ...options };
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends a `StructFieldDefinition` to the `Struct
|
||||
*/
|
||||
public field<
|
||||
field<
|
||||
TName extends PropertyKey,
|
||||
TDefinition extends StructFieldDefinition<any, any, any>,
|
||||
>(
|
||||
|
@ -304,7 +304,7 @@ export class Struct<
|
|||
/**
|
||||
* Merges (flats) another `Struct`'s fields and extra fields into this one.
|
||||
*/
|
||||
public concat<TOther extends Struct<any, any, any, any>>(
|
||||
concat<TOther extends Struct<any, any, any, any>>(
|
||||
other: TOther,
|
||||
): Struct<
|
||||
TFields & TOther["TFields"],
|
||||
|
@ -323,7 +323,7 @@ export class Struct<
|
|||
return this as any;
|
||||
}
|
||||
|
||||
private number<
|
||||
#number<
|
||||
TName extends PropertyKey,
|
||||
TType extends NumberFieldType = NumberFieldType,
|
||||
TTypeScriptType = number,
|
||||
|
@ -337,64 +337,64 @@ export class Struct<
|
|||
/**
|
||||
* Appends an `int8` field to the `Struct`
|
||||
*/
|
||||
public int8<TName extends PropertyKey, TTypeScriptType = number>(
|
||||
int8<TName extends PropertyKey, TTypeScriptType = number>(
|
||||
name: TName,
|
||||
typeScriptType?: TTypeScriptType,
|
||||
) {
|
||||
return this.number(name, NumberFieldType.Int8, typeScriptType);
|
||||
return this.#number(name, NumberFieldType.Int8, typeScriptType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends an `uint8` field to the `Struct`
|
||||
*/
|
||||
public uint8<TName extends PropertyKey, TTypeScriptType = number>(
|
||||
uint8<TName extends PropertyKey, TTypeScriptType = number>(
|
||||
name: TName,
|
||||
typeScriptType?: TTypeScriptType,
|
||||
) {
|
||||
return this.number(name, NumberFieldType.Uint8, typeScriptType);
|
||||
return this.#number(name, NumberFieldType.Uint8, typeScriptType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends an `int16` field to the `Struct`
|
||||
*/
|
||||
public int16<TName extends PropertyKey, TTypeScriptType = number>(
|
||||
int16<TName extends PropertyKey, TTypeScriptType = number>(
|
||||
name: TName,
|
||||
typeScriptType?: TTypeScriptType,
|
||||
) {
|
||||
return this.number(name, NumberFieldType.Int16, typeScriptType);
|
||||
return this.#number(name, NumberFieldType.Int16, typeScriptType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends an `uint16` field to the `Struct`
|
||||
*/
|
||||
public uint16<TName extends PropertyKey, TTypeScriptType = number>(
|
||||
uint16<TName extends PropertyKey, TTypeScriptType = number>(
|
||||
name: TName,
|
||||
typeScriptType?: TTypeScriptType,
|
||||
) {
|
||||
return this.number(name, NumberFieldType.Uint16, typeScriptType);
|
||||
return this.#number(name, NumberFieldType.Uint16, typeScriptType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends an `int32` field to the `Struct`
|
||||
*/
|
||||
public int32<TName extends PropertyKey, TTypeScriptType = number>(
|
||||
int32<TName extends PropertyKey, TTypeScriptType = number>(
|
||||
name: TName,
|
||||
typeScriptType?: TTypeScriptType,
|
||||
) {
|
||||
return this.number(name, NumberFieldType.Int32, typeScriptType);
|
||||
return this.#number(name, NumberFieldType.Int32, typeScriptType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends an `uint32` field to the `Struct`
|
||||
*/
|
||||
public uint32<TName extends PropertyKey, TTypeScriptType = number>(
|
||||
uint32<TName extends PropertyKey, TTypeScriptType = number>(
|
||||
name: TName,
|
||||
typeScriptType?: TTypeScriptType,
|
||||
) {
|
||||
return this.number(name, NumberFieldType.Uint32, typeScriptType);
|
||||
return this.#number(name, NumberFieldType.Uint32, typeScriptType);
|
||||
}
|
||||
|
||||
private bigint<
|
||||
#bigint<
|
||||
TName extends PropertyKey,
|
||||
TType extends BigIntFieldType = BigIntFieldType,
|
||||
TTypeScriptType = TType["TTypeScriptType"],
|
||||
|
@ -410,11 +410,11 @@ export class Struct<
|
|||
*
|
||||
* Requires native `BigInt` support
|
||||
*/
|
||||
public int64<
|
||||
int64<
|
||||
TName extends PropertyKey,
|
||||
TTypeScriptType = BigIntFieldType["TTypeScriptType"],
|
||||
>(name: TName, typeScriptType?: TTypeScriptType) {
|
||||
return this.bigint(name, BigIntFieldType.Int64, typeScriptType);
|
||||
return this.#bigint(name, BigIntFieldType.Int64, typeScriptType);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -422,14 +422,14 @@ export class Struct<
|
|||
*
|
||||
* Requires native `BigInt` support
|
||||
*/
|
||||
public uint64<
|
||||
uint64<
|
||||
TName extends PropertyKey,
|
||||
TTypeScriptType = BigIntFieldType["TTypeScriptType"],
|
||||
>(name: TName, typeScriptType?: TTypeScriptType) {
|
||||
return this.bigint(name, BigIntFieldType.Uint64, typeScriptType);
|
||||
return this.#bigint(name, BigIntFieldType.Uint64, typeScriptType);
|
||||
}
|
||||
|
||||
private arrayBufferLike: ArrayBufferLikeFieldCreator<
|
||||
#arrayBufferLike: ArrayBufferLikeFieldCreator<
|
||||
TFields,
|
||||
TOmitInitKey,
|
||||
TExtra,
|
||||
|
@ -454,14 +454,14 @@ export class Struct<
|
|||
}
|
||||
};
|
||||
|
||||
public uint8Array: BoundArrayBufferLikeFieldDefinitionCreator<
|
||||
uint8Array: BoundArrayBufferLikeFieldDefinitionCreator<
|
||||
TFields,
|
||||
TOmitInitKey,
|
||||
TExtra,
|
||||
TPostDeserialized,
|
||||
Uint8ArrayBufferFieldSubType
|
||||
> = (name: PropertyKey, options: any, typeScriptType: any): any => {
|
||||
return this.arrayBufferLike(
|
||||
return this.#arrayBufferLike(
|
||||
name,
|
||||
Uint8ArrayBufferFieldSubType.Instance,
|
||||
options,
|
||||
|
@ -469,14 +469,14 @@ export class Struct<
|
|||
);
|
||||
};
|
||||
|
||||
public string: BoundArrayBufferLikeFieldDefinitionCreator<
|
||||
string: BoundArrayBufferLikeFieldDefinitionCreator<
|
||||
TFields,
|
||||
TOmitInitKey,
|
||||
TExtra,
|
||||
TPostDeserialized,
|
||||
StringBufferFieldSubType
|
||||
> = (name: PropertyKey, options: any, typeScriptType: any): any => {
|
||||
return this.arrayBufferLike(
|
||||
return this.#arrayBufferLike(
|
||||
name,
|
||||
StringBufferFieldSubType.Instance,
|
||||
options,
|
||||
|
@ -494,7 +494,7 @@ export class Struct<
|
|||
* @param value
|
||||
* An object containing properties to be added to the result value. Accessors and methods are also allowed.
|
||||
*/
|
||||
public extra<
|
||||
extra<
|
||||
T extends Record<
|
||||
// This trick disallows any keys that are already in `TValue`
|
||||
Exclude<keyof T, Exclude<keyof T, keyof TFields>>,
|
||||
|
@ -516,7 +516,7 @@ export class Struct<
|
|||
* A callback returning `never` (always throw an error)
|
||||
* will also change the return type of `deserialize` to `never`.
|
||||
*/
|
||||
public postDeserialize(
|
||||
postDeserialize(
|
||||
callback: StructPostDeserialized<TFields, never>,
|
||||
): Struct<TFields, TOmitInitKey, TExtra, never>;
|
||||
/**
|
||||
|
@ -525,7 +525,7 @@ export class Struct<
|
|||
* A callback returning `void` means it modify the result object in-place
|
||||
* (or doesn't modify it at all), so `deserialize` will still return the result object.
|
||||
*/
|
||||
public postDeserialize(
|
||||
postDeserialize(
|
||||
callback?: StructPostDeserialized<TFields, void>,
|
||||
): Struct<TFields, TOmitInitKey, TExtra, undefined>;
|
||||
/**
|
||||
|
@ -534,10 +534,10 @@ export class Struct<
|
|||
* A callback returning anything other than `undefined`
|
||||
* will `deserialize` to return that object instead.
|
||||
*/
|
||||
public postDeserialize<TPostSerialize>(
|
||||
postDeserialize<TPostSerialize>(
|
||||
callback?: StructPostDeserialized<TFields, TPostSerialize>,
|
||||
): Struct<TFields, TOmitInitKey, TExtra, TPostSerialize>;
|
||||
public postDeserialize(callback?: StructPostDeserialized<TFields, any>) {
|
||||
postDeserialize(callback?: StructPostDeserialized<TFields, any>) {
|
||||
this.#postDeserialized = callback;
|
||||
return this as any;
|
||||
}
|
||||
|
@ -545,13 +545,13 @@ export class Struct<
|
|||
/**
|
||||
* Deserialize a struct value from `stream`.
|
||||
*/
|
||||
public deserialize(
|
||||
deserialize(
|
||||
stream: ExactReadable,
|
||||
): StructDeserializedResult<TFields, TExtra, TPostDeserialized>;
|
||||
public deserialize(
|
||||
deserialize(
|
||||
stream: AsyncExactReadable,
|
||||
): Promise<StructDeserializedResult<TFields, TExtra, TPostDeserialized>>;
|
||||
public deserialize(
|
||||
deserialize(
|
||||
stream: ExactReadable | AsyncExactReadable,
|
||||
): ValueOrPromise<
|
||||
StructDeserializedResult<TFields, TExtra, TPostDeserialized>
|
||||
|
@ -603,12 +603,12 @@ export class Struct<
|
|||
.valueOrPromise();
|
||||
}
|
||||
|
||||
public serialize(init: Evaluate<Omit<TFields, TOmitInitKey>>): Uint8Array;
|
||||
public serialize(
|
||||
serialize(init: Evaluate<Omit<TFields, TOmitInitKey>>): Uint8Array;
|
||||
serialize(
|
||||
init: Evaluate<Omit<TFields, TOmitInitKey>>,
|
||||
output: Uint8Array,
|
||||
): number;
|
||||
public serialize(
|
||||
serialize(
|
||||
init: Evaluate<Omit<TFields, TOmitInitKey>>,
|
||||
output?: Uint8Array,
|
||||
): Uint8Array | number {
|
||||
|
|
|
@ -57,11 +57,11 @@ export const SyncPromise: SyncPromiseStatic = {
|
|||
class PendingSyncPromise<T> implements SyncPromise<T> {
|
||||
#promise: PromiseLike<T>;
|
||||
|
||||
public constructor(promise: PromiseLike<T>) {
|
||||
constructor(promise: PromiseLike<T>) {
|
||||
this.#promise = promise;
|
||||
}
|
||||
|
||||
public then<TResult1 = T, TResult2 = never>(
|
||||
then<TResult1 = T, TResult2 = never>(
|
||||
onfulfilled?:
|
||||
| ((value: T) => TResult1 | PromiseLike<TResult1>)
|
||||
| null
|
||||
|
@ -76,7 +76,7 @@ class PendingSyncPromise<T> implements SyncPromise<T> {
|
|||
);
|
||||
}
|
||||
|
||||
public valueOrPromise(): T | PromiseLike<T> {
|
||||
valueOrPromise(): T | PromiseLike<T> {
|
||||
return this.#promise;
|
||||
}
|
||||
}
|
||||
|
@ -84,11 +84,11 @@ class PendingSyncPromise<T> implements SyncPromise<T> {
|
|||
class ResolvedSyncPromise<T> implements SyncPromise<T> {
|
||||
#value: T;
|
||||
|
||||
public constructor(value: T) {
|
||||
constructor(value: T) {
|
||||
this.#value = value;
|
||||
}
|
||||
|
||||
public then<TResult1 = T>(
|
||||
then<TResult1 = T>(
|
||||
onfulfilled?:
|
||||
| ((value: T) => TResult1 | PromiseLike<TResult1>)
|
||||
| null
|
||||
|
@ -100,7 +100,7 @@ class ResolvedSyncPromise<T> implements SyncPromise<T> {
|
|||
return SyncPromise.try(() => onfulfilled(this.#value));
|
||||
}
|
||||
|
||||
public valueOrPromise(): T | PromiseLike<T> {
|
||||
valueOrPromise(): T | PromiseLike<T> {
|
||||
return this.#value;
|
||||
}
|
||||
}
|
||||
|
@ -108,11 +108,11 @@ class ResolvedSyncPromise<T> implements SyncPromise<T> {
|
|||
class RejectedSyncPromise<T> implements SyncPromise<T> {
|
||||
#reason: any;
|
||||
|
||||
public constructor(reason: any) {
|
||||
constructor(reason: any) {
|
||||
this.#reason = reason;
|
||||
}
|
||||
|
||||
public then<TResult1 = T, TResult2 = never>(
|
||||
then<TResult1 = T, TResult2 = never>(
|
||||
onfulfilled?:
|
||||
| ((value: T) => TResult1 | PromiseLike<TResult1>)
|
||||
| null
|
||||
|
@ -128,7 +128,7 @@ class RejectedSyncPromise<T> implements SyncPromise<T> {
|
|||
return SyncPromise.try(() => onrejected(this.#reason));
|
||||
}
|
||||
|
||||
public valueOrPromise(): T | PromiseLike<T> {
|
||||
valueOrPromise(): T | PromiseLike<T> {
|
||||
throw this.#reason;
|
||||
}
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue