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