refactor: migrate to ES private fields

This commit is contained in:
Simon Chan 2023-07-11 21:05:39 +08:00
parent 433f9b986f
commit 7056feb3b1
No known key found for this signature in database
GPG key ID: A8B69F750B9BCEDD
108 changed files with 1264 additions and 1294 deletions

View file

@ -71,7 +71,7 @@ export default class AdbWebCredentialStore implements AdbCredentialStore {
*
* @returns The private key in PKCS #8 format.
*/
public async generateKey(): Promise<Uint8Array> {
async generateKey(): Promise<Uint8Array> {
const { privateKey: cryptoKey } = await crypto.subtle.generateKey(
{
name: "RSASSA-PKCS1-v1_5",
@ -97,7 +97,7 @@ export default class AdbWebCredentialStore implements AdbCredentialStore {
*
* This method returns a generator, so `for await...of...` loop should be used to read the key.
*/
public async *iterateKeys(): AsyncGenerator<Uint8Array, void, void> {
async *iterateKeys(): AsyncGenerator<Uint8Array, void, void> {
for (const key of await getAllKeys()) {
yield key;
}

View file

@ -46,26 +46,26 @@ declare global {
}
export default class AdbDaemonDirectSocketsDevice implements AdbDaemonDevice {
public static isSupported(): boolean {
static isSupported(): boolean {
return typeof globalThis.TCPSocket !== "undefined";
}
public readonly serial: string;
readonly serial: string;
public readonly host: string;
readonly host: string;
public readonly port: number;
readonly port: number;
public name: string | undefined;
name: string | undefined;
public constructor(host: string, port = 5555, name?: string) {
constructor(host: string, port = 5555, name?: string) {
this.host = host;
this.port = port;
this.serial = `${host}:${port}`;
this.name = name;
}
public async connect() {
async connect() {
const socket = new globalThis.TCPSocket(this.host, this.port, {
noDelay: true,
});

View file

@ -77,16 +77,16 @@ class Uint8ArrayExactReadable implements ExactReadable {
#data: Uint8Array;
#position: number;
public get position() {
get position() {
return this.#position;
}
public constructor(data: Uint8Array) {
constructor(data: Uint8Array) {
this.#data = data;
this.#position = 0;
}
public readExactly(length: number): Uint8Array {
readExactly(length: number): Uint8Array {
const result = this.#data.subarray(
this.#position,
this.#position + length,
@ -100,16 +100,16 @@ export class AdbDaemonWebUsbConnection
implements ReadableWritablePair<AdbPacketData, Consumable<AdbPacketInit>>
{
#readable: ReadableStream<AdbPacketData>;
public get readable() {
get readable() {
return this.#readable;
}
#writable: WritableStream<Consumable<AdbPacketInit>>;
public get writable() {
get writable() {
return this.#writable;
}
public constructor(
constructor(
device: USBDevice,
inEndpoint: USBEndpoint,
outEndpoint: USBEndpoint,
@ -249,15 +249,15 @@ export class AdbDaemonWebUsbDevice implements AdbDaemonDevice {
#usbManager: USB;
#raw: USBDevice;
public get raw() {
get raw() {
return this.#raw;
}
public get serial(): string {
get serial(): string {
return this.#raw.serialNumber!;
}
public get name(): string {
get name(): string {
return this.#raw.productName!;
}
@ -267,7 +267,7 @@ export class AdbDaemonWebUsbDevice implements AdbDaemonDevice {
* @param device The `USBDevice` instance obtained elsewhere.
* @param filters The filters to use when searching for ADB interface. Defaults to {@link ADB_DEFAULT_DEVICE_FILTER}.
*/
public constructor(
constructor(
device: USBDevice,
filters: AdbDeviceFilter[] = [ADB_DEFAULT_DEVICE_FILTER],
usbManager: USB,
@ -281,7 +281,7 @@ export class AdbDaemonWebUsbDevice implements AdbDaemonDevice {
* Claim the device and create a pair of `AdbPacket` streams to the ADB interface.
* @returns The pair of `AdbPacket` streams.
*/
public async connect(): Promise<
async connect(): Promise<
ReadableWritablePair<AdbPacketData, Consumable<AdbPacketInit>>
> {
if (!this.#raw.opened) {

View file

@ -8,7 +8,7 @@ export class AdbDaemonWebUsbDeviceManager {
*
* May be `undefined` if current runtime does not support WebUSB.
*/
public static readonly BROWSER =
static readonly BROWSER =
typeof globalThis.navigator !== "undefined" &&
!!globalThis.navigator.usb
? new AdbDaemonWebUsbDeviceManager(globalThis.navigator.usb)
@ -20,7 +20,7 @@ export class AdbDaemonWebUsbDeviceManager {
* Create a new instance of {@link AdbDaemonWebUsbDeviceManager} using the specified WebUSB implementation.
* @param usbManager A WebUSB compatible interface.
*/
public constructor(usbManager: USB) {
constructor(usbManager: USB) {
this.#usbManager = usbManager;
}
@ -37,7 +37,7 @@ export class AdbDaemonWebUsbDeviceManager {
* @returns An {@link AdbDaemonWebUsbDevice} instance if the user selected a device,
* or `undefined` if the user cancelled the device picker.
*/
public async requestDevice(
async requestDevice(
filters: AdbDeviceFilter[] = [ADB_DEFAULT_DEVICE_FILTER],
): Promise<AdbDaemonWebUsbDevice | undefined> {
try {
@ -67,7 +67,7 @@ export class AdbDaemonWebUsbDeviceManager {
* Defaults to {@link ADB_DEFAULT_DEVICE_FILTER}.
* @returns An array of {@link AdbDaemonWebUsbDevice} instances for all connected and authenticated devices.
*/
public async getDevices(
async getDevices(
filters: AdbDeviceFilter[] = [ADB_DEFAULT_DEVICE_FILTER],
): Promise<AdbDaemonWebUsbDevice[]> {
const devices = await this.#usbManager.getDevices();

View file

@ -2,27 +2,27 @@ export class AdbDaemonWebUsbDeviceWatcher {
#callback: (newDeviceSerial?: string) => void;
#usbManager: USB;
public constructor(callback: (newDeviceSerial?: string) => void, usb: USB) {
constructor(callback: (newDeviceSerial?: string) => void, usb: USB) {
this.#callback = callback;
this.#usbManager = usb;
this.#usbManager.addEventListener("connect", this.handleConnect);
this.#usbManager.addEventListener("disconnect", this.handleDisconnect);
this.#usbManager.addEventListener("connect", this.#handleConnect);
this.#usbManager.addEventListener("disconnect", this.#handleDisconnect);
}
public dispose(): void {
this.#usbManager.removeEventListener("connect", this.handleConnect);
dispose(): void {
this.#usbManager.removeEventListener("connect", this.#handleConnect);
this.#usbManager.removeEventListener(
"disconnect",
this.handleDisconnect,
this.#handleDisconnect,
);
}
private handleConnect = (e: USBConnectionEvent) => {
#handleConnect = (e: USBConnectionEvent) => {
this.#callback(e.device.serialNumber);
};
private handleDisconnect = () => {
#handleDisconnect = () => {
this.#callback();
};
}

View file

@ -14,16 +14,16 @@ import {
} from "@yume-chan/stream-extra";
export default class AdbDaemonWebSocketDevice implements AdbDaemonDevice {
public readonly serial: string;
readonly serial: string;
public name: string | undefined;
name: string | undefined;
public constructor(url: string, name?: string) {
constructor(url: string, name?: string) {
this.serial = url;
this.name = name;
}
public async connect() {
async connect() {
const socket = new WebSocket(this.serial);
socket.binaryType = "arraybuffer";

View file

@ -62,9 +62,9 @@ function concatStreams<T>(...streams: ReadableStream<T>[]): ReadableStream<T> {
}
export class AdbScrcpyExitedError extends Error {
public output: string[];
output: string[];
public constructor(output: string[]) {
constructor(output: string[]) {
super("scrcpy server exited prematurely");
this.output = output;
}
@ -98,7 +98,7 @@ export type AdbScrcpyAudioStreamMetadata =
| AdbScrcpyAudioStreamSuccessMetadata;
export class AdbScrcpyClient {
public static async pushServer(
static async pushServer(
adb: Adb,
file: ReadableStream<Consumable<Uint8Array>>,
filename = DEFAULT_SERVER_PATH,
@ -114,7 +114,7 @@ export class AdbScrcpyClient {
}
}
public static async start(
static async start(
adb: Adb,
path: string,
version: string,
@ -213,7 +213,7 @@ export class AdbScrcpyClient {
* This method will modify the given `options`,
* so don't reuse it elsewhere.
*/
public static async getEncoders(
static async getEncoders(
adb: Adb,
path: string,
version: string,
@ -227,7 +227,7 @@ export class AdbScrcpyClient {
* This method will modify the given `options`,
* so don't reuse it elsewhere.
*/
public static async getDisplays(
static async getDisplays(
adb: Adb,
path: string,
version: string,
@ -237,51 +237,49 @@ export class AdbScrcpyClient {
return await options.getDisplays(adb, path, version);
}
private _options: AdbScrcpyOptions<object>;
private _process: AdbSubprocessProtocol;
#options: AdbScrcpyOptions<object>;
#process: AdbSubprocessProtocol;
private _stdout: ReadableStream<string>;
public get stdout() {
return this._stdout;
#stdout: ReadableStream<string>;
get stdout() {
return this.#stdout;
}
public get exit() {
return this._process.exit;
get exit() {
return this.#process.exit;
}
private _screenWidth: number | undefined;
public get screenWidth() {
return this._screenWidth;
#screenWidth: number | undefined;
get screenWidth() {
return this.#screenWidth;
}
private _screenHeight: number | undefined;
public get screenHeight() {
return this._screenHeight;
#screenHeight: number | undefined;
get screenHeight() {
return this.#screenHeight;
}
private _videoStream: Promise<AdbScrcpyVideoStream> | undefined;
public get videoStream() {
return this._videoStream;
#videoStream: Promise<AdbScrcpyVideoStream> | undefined;
get videoStream() {
return this.#videoStream;
}
private _audioStream: Promise<AdbScrcpyAudioStreamMetadata> | undefined;
public get audioStream() {
return this._audioStream;
#audioStream: Promise<AdbScrcpyAudioStreamMetadata> | undefined;
get audioStream() {
return this.#audioStream;
}
private _controlMessageWriter: ScrcpyControlMessageWriter | undefined;
public get controlMessageWriter() {
return this._controlMessageWriter;
#controlMessageWriter: ScrcpyControlMessageWriter | undefined;
get controlMessageWriter() {
return this.#controlMessageWriter;
}
private _deviceMessageStream:
| ReadableStream<ScrcpyDeviceMessage>
| undefined;
public get deviceMessageStream() {
return this._deviceMessageStream;
#deviceMessageStream: ReadableStream<ScrcpyDeviceMessage> | undefined;
get deviceMessageStream() {
return this.#deviceMessageStream;
}
public constructor({
constructor({
options,
process,
stdout,
@ -289,36 +287,36 @@ export class AdbScrcpyClient {
audioStream,
controlStream,
}: AdbScrcpyClientInit) {
this._options = options;
this._process = process;
this._stdout = stdout;
this.#options = options;
this.#process = process;
this.#stdout = stdout;
this._videoStream = videoStream
? this.createVideoStream(videoStream)
this.#videoStream = videoStream
? this.#createVideoStream(videoStream)
: undefined;
this._audioStream = audioStream
? this.createAudioStream(audioStream)
this.#audioStream = audioStream
? this.#createAudioStream(audioStream)
: undefined;
if (controlStream) {
this._controlMessageWriter = new ScrcpyControlMessageWriter(
this.#controlMessageWriter = new ScrcpyControlMessageWriter(
controlStream.writable.getWriter(),
options,
);
this._deviceMessageStream = controlStream.readable.pipeThrough(
this.#deviceMessageStream = controlStream.readable.pipeThrough(
new ScrcpyDeviceMessageDeserializeStream(),
);
}
}
private async createVideoStream(initialStream: ReadableStream<Uint8Array>) {
async #createVideoStream(initialStream: ReadableStream<Uint8Array>) {
const { stream, metadata } =
await this._options.parseVideoStreamMetadata(initialStream);
await this.#options.parseVideoStreamMetadata(initialStream);
return {
stream: stream
.pipeThrough(this._options.createMediaStreamTransformer())
.pipeThrough(this.#options.createMediaStreamTransformer())
.pipeThrough(
new InspectStream((packet) => {
if (packet.type === "configuration") {
@ -327,16 +325,16 @@ export class AdbScrcpyClient {
const { croppedWidth, croppedHeight } =
h264ParseConfiguration(packet.data);
this._screenWidth = croppedWidth;
this._screenHeight = croppedHeight;
this.#screenWidth = croppedWidth;
this.#screenHeight = croppedHeight;
break;
}
case ScrcpyVideoCodecId.H265: {
const { croppedWidth, croppedHeight } =
h265ParseConfiguration(packet.data);
this._screenWidth = croppedWidth;
this._screenHeight = croppedHeight;
this.#screenWidth = croppedWidth;
this.#screenHeight = croppedHeight;
break;
}
}
@ -347,10 +345,10 @@ export class AdbScrcpyClient {
};
}
private async createAudioStream(
async #createAudioStream(
initialStream: ReadableStream<Uint8Array>,
): Promise<AdbScrcpyAudioStreamMetadata> {
const metadata = await this._options.parseAudioStreamMetadata(
const metadata = await this.#options.parseAudioStreamMetadata(
initialStream,
);
@ -362,7 +360,7 @@ export class AdbScrcpyClient {
return {
...metadata,
stream: metadata.stream.pipeThrough(
this._options.createMediaStreamTransformer(),
this.#options.createMediaStreamTransformer(),
),
};
default:
@ -374,7 +372,7 @@ export class AdbScrcpyClient {
}
}
public async close() {
await this._process.kill();
async close() {
await this.#process.kill();
}
}

View file

@ -47,13 +47,13 @@ export abstract class AdbScrcpyConnection implements Disposable {
protected socketName: string;
public constructor(adb: Adb, options: AdbScrcpyConnectionOptions) {
constructor(adb: Adb, options: AdbScrcpyConnectionOptions) {
this.adb = adb;
this.options = options;
this.socketName = this.getSocketName();
}
public initialize(): ValueOrPromise<void> {
initialize(): ValueOrPromise<void> {
// pure virtual method
}
@ -65,28 +65,28 @@ export abstract class AdbScrcpyConnection implements Disposable {
return socketName;
}
public abstract getStreams(): ValueOrPromise<AdbScrcpyConnectionStreams>;
abstract getStreams(): ValueOrPromise<AdbScrcpyConnectionStreams>;
public dispose(): void {
dispose(): void {
// pure virtual method
}
}
export class AdbScrcpyForwardConnection extends AdbScrcpyConnection {
private _disposed = false;
#disposed = false;
private connect(): Promise<
#connect(): Promise<
ReadableWritablePair<Uint8Array, Consumable<Uint8Array>>
> {
return this.adb.createSocket(this.socketName);
}
private async connectAndRetry(
async #connectAndRetry(
sendDummyByte: boolean,
): Promise<ReadableWritablePair<Uint8Array, Consumable<Uint8Array>>> {
for (let i = 0; !this._disposed && i < 100; i += 1) {
for (let i = 0; !this.#disposed && i < 100; i += 1) {
try {
const stream = await this.connect();
const stream = await this.#connect();
if (sendDummyByte) {
// Can't guarantee the stream will preserve message boundaries,
// so buffer the stream
@ -108,25 +108,25 @@ export class AdbScrcpyForwardConnection extends AdbScrcpyConnection {
throw new Error(`Can't connect to server after 100 retries`);
}
public override async getStreams(): Promise<AdbScrcpyConnectionStreams> {
override async getStreams(): Promise<AdbScrcpyConnectionStreams> {
let { sendDummyByte } = this.options;
const streams: AdbScrcpyConnectionStreams = {};
if (this.options.video) {
const video = await this.connectAndRetry(sendDummyByte);
const video = await this.#connectAndRetry(sendDummyByte);
streams.video = video.readable;
sendDummyByte = false;
}
if (this.options.audio) {
const audio = await this.connectAndRetry(sendDummyByte);
const audio = await this.#connectAndRetry(sendDummyByte);
streams.audio = audio.readable;
sendDummyByte = false;
}
if (this.options.control) {
const control = await this.connectAndRetry(sendDummyByte);
const control = await this.#connectAndRetry(sendDummyByte);
sendDummyByte = false;
streams.control = control;
}
@ -134,19 +134,19 @@ export class AdbScrcpyForwardConnection extends AdbScrcpyConnection {
return streams;
}
public override dispose(): void {
this._disposed = true;
override dispose(): void {
this.#disposed = true;
}
}
export class AdbScrcpyReverseConnection extends AdbScrcpyConnection {
private streams!: ReadableStreamDefaultReader<
#streams!: ReadableStreamDefaultReader<
ReadableWritablePair<Uint8Array, Consumable<Uint8Array>>
>;
private address!: string;
#address!: string;
public override async initialize(): Promise<void> {
override async initialize(): Promise<void> {
// try to unbind first
await this.adb.reverse.remove(this.socketName).catch((e) => {
if (e instanceof AdbReverseNotSupportedError) {
@ -160,45 +160,48 @@ export class AdbScrcpyReverseConnection extends AdbScrcpyConnection {
ReadableWritablePair<Uint8Array, Consumable<Uint8Array>>,
ReadableWritablePair<Uint8Array, Consumable<Uint8Array>>
>();
this.streams = queue.readable.getReader();
this.#streams = queue.readable.getReader();
const writer = queue.writable.getWriter();
this.address = await this.adb.reverse.add(this.socketName, (socket) => {
this.#address = await this.adb.reverse.add(
this.socketName,
(socket) => {
void writer.write(socket);
});
},
);
}
private async accept(): Promise<
async #accept(): Promise<
ReadableWritablePair<Uint8Array, Consumable<Uint8Array>>
> {
return (await this.streams.read()).value!;
return (await this.#streams.read()).value!;
}
public async getStreams(): Promise<AdbScrcpyConnectionStreams> {
async getStreams(): Promise<AdbScrcpyConnectionStreams> {
const streams: AdbScrcpyConnectionStreams = {};
if (this.options.video) {
const video = await this.accept();
const video = await this.#accept();
streams.video = video.readable;
}
if (this.options.audio) {
const audio = await this.accept();
const audio = await this.#accept();
streams.audio = audio.readable;
}
if (this.options.control) {
const control = await this.accept();
const control = await this.#accept();
streams.control = control;
}
return streams;
}
public override dispose() {
override dispose() {
// Don't await this!
// `reverse.remove`'s response will never arrive
// before we read all pending data from Scrcpy streams
// NOOP: failed to remove reverse tunnel is not a big deal
this.adb.reverse.remove(this.address).catch(NOOP);
this.adb.reverse.remove(this.#address).catch(NOOP);
}
}

View file

@ -20,7 +20,7 @@ import type { AdbScrcpyOptions } from "./types.js";
import { AdbScrcpyOptionsBase } from "./types.js";
export class AdbScrcpyOptions1_16 extends AdbScrcpyOptionsBase<ScrcpyOptionsInit1_16> {
public static createConnection(
static createConnection(
adb: Adb,
connectionOptions: AdbScrcpyConnectionOptions,
tunnelForward: boolean,
@ -32,7 +32,7 @@ export class AdbScrcpyOptions1_16 extends AdbScrcpyOptionsBase<ScrcpyOptionsInit
}
}
public static async getEncoders(
static async getEncoders(
adb: Adb,
path: string,
version: string,
@ -55,7 +55,7 @@ export class AdbScrcpyOptions1_16 extends AdbScrcpyOptionsBase<ScrcpyOptionsInit
return encoders;
}
public static async getDisplays(
static async getDisplays(
adb: Adb,
path: string,
version: string,
@ -90,7 +90,7 @@ export class AdbScrcpyOptions1_16 extends AdbScrcpyOptionsBase<ScrcpyOptionsInit
}
}
public override getEncoders(
override getEncoders(
adb: Adb,
path: string,
version: string,
@ -98,7 +98,7 @@ export class AdbScrcpyOptions1_16 extends AdbScrcpyOptionsBase<ScrcpyOptionsInit
return AdbScrcpyOptions1_16.getEncoders(adb, path, version, this);
}
public override getDisplays(
override getDisplays(
adb: Adb,
path: string,
version: string,
@ -106,7 +106,7 @@ export class AdbScrcpyOptions1_16 extends AdbScrcpyOptionsBase<ScrcpyOptionsInit
return AdbScrcpyOptions1_16.getDisplays(adb, path, version, this);
}
public override createConnection(adb: Adb): AdbScrcpyConnection {
override createConnection(adb: Adb): AdbScrcpyConnection {
return AdbScrcpyOptions1_16.createConnection(
adb,
{

View file

@ -11,7 +11,7 @@ import { AdbScrcpyOptions1_16 } from "./1_16.js";
import { AdbScrcpyOptionsBase } from "./types.js";
export class AdbScrcpyOptions1_22 extends AdbScrcpyOptionsBase<ScrcpyOptionsInit1_22> {
public override getEncoders(
override getEncoders(
adb: Adb,
path: string,
version: string,
@ -19,7 +19,7 @@ export class AdbScrcpyOptions1_22 extends AdbScrcpyOptionsBase<ScrcpyOptionsInit
return AdbScrcpyOptions1_16.getEncoders(adb, path, version, this);
}
public override getDisplays(
override getDisplays(
adb: Adb,
path: string,
version: string,
@ -27,7 +27,7 @@ export class AdbScrcpyOptions1_22 extends AdbScrcpyOptionsBase<ScrcpyOptionsInit
return AdbScrcpyOptions1_16.getDisplays(adb, path, version, this);
}
public override createConnection(adb: Adb): AdbScrcpyConnection {
override createConnection(adb: Adb): AdbScrcpyConnection {
return AdbScrcpyOptions1_16.createConnection(
adb,
{

View file

@ -13,7 +13,7 @@ import type { AdbScrcpyOptions } from "./types.js";
import { AdbScrcpyOptionsBase } from "./types.js";
export class AdbScrcpyOptions2_0 extends AdbScrcpyOptionsBase<ScrcpyOptionsInit2_0> {
public static async getEncoders(
static async getEncoders(
adb: Adb,
path: string,
version: string,
@ -48,7 +48,7 @@ export class AdbScrcpyOptions2_0 extends AdbScrcpyOptionsBase<ScrcpyOptionsInit2
}
}
public override async getEncoders(
override async getEncoders(
adb: Adb,
path: string,
version: string,
@ -56,7 +56,7 @@ export class AdbScrcpyOptions2_0 extends AdbScrcpyOptionsBase<ScrcpyOptionsInit2
return AdbScrcpyOptions2_0.getEncoders(adb, path, version, this);
}
public override getDisplays(
override getDisplays(
adb: Adb,
path: string,
version: string,
@ -64,7 +64,7 @@ export class AdbScrcpyOptions2_0 extends AdbScrcpyOptionsBase<ScrcpyOptionsInit2
return AdbScrcpyOptions1_16.getDisplays(adb, path, version, this);
}
public override createConnection(adb: Adb): AdbScrcpyConnection {
override createConnection(adb: Adb): AdbScrcpyConnection {
return AdbScrcpyOptions1_16.createConnection(
adb,
{

View file

@ -12,7 +12,7 @@ import { AdbScrcpyOptions2_0 } from "./2_0.js";
import { AdbScrcpyOptionsBase } from "./types.js";
export class AdbScrcpyOptions2_1 extends AdbScrcpyOptionsBase<ScrcpyOptionsInit2_1> {
public override async getEncoders(
override async getEncoders(
adb: Adb,
path: string,
version: string,
@ -20,7 +20,7 @@ export class AdbScrcpyOptions2_1 extends AdbScrcpyOptionsBase<ScrcpyOptionsInit2
return AdbScrcpyOptions2_0.getEncoders(adb, path, version, this);
}
public override getDisplays(
override getDisplays(
adb: Adb,
path: string,
version: string,
@ -28,7 +28,7 @@ export class AdbScrcpyOptions2_1 extends AdbScrcpyOptionsBase<ScrcpyOptionsInit2
return AdbScrcpyOptions1_16.getDisplays(adb, path, version, this);
}
public override createConnection(adb: Adb): AdbScrcpyConnection {
override createConnection(adb: Adb): AdbScrcpyConnection {
return AdbScrcpyOptions1_16.createConnection(
adb,
{

View file

@ -34,31 +34,31 @@ export abstract class AdbScrcpyOptionsBase<T extends object>
extends ScrcpyOptionsBase<T, ScrcpyOptions<T>>
implements AdbScrcpyOptions<T>
{
public override get defaults(): Required<T> {
override get defaults(): Required<T> {
return this._base.defaults;
}
public tunnelForwardOverride = false;
tunnelForwardOverride = false;
public constructor(base: ScrcpyOptions<T>) {
constructor(base: ScrcpyOptions<T>) {
super(base, base.value);
}
public serialize(): string[] {
serialize(): string[] {
return this._base.serialize();
}
public abstract getEncoders(
abstract getEncoders(
adb: Adb,
path: string,
version: string,
): Promise<ScrcpyEncoder[]>;
public abstract getDisplays(
abstract getDisplays(
adb: Adb,
path: string,
version: string,
): Promise<ScrcpyDisplay[]>;
public abstract createConnection(adb: Adb): AdbScrcpyConnection;
abstract createConnection(adb: Adb): AdbScrcpyConnection;
}

View file

@ -58,15 +58,15 @@ function nodeSocketToStreamPair(socket: Socket) {
}
export class AdbServerNodeTcpConnection implements AdbServerConnection {
public readonly spec: SocketConnectOpts;
readonly spec: SocketConnectOpts;
private readonly _listeners = new Map<string, Server>();
readonly #listeners = new Map<string, Server>();
public constructor(spec: SocketConnectOpts) {
constructor(spec: SocketConnectOpts) {
this.spec = spec;
}
public async connect(
async connect(
{ unref }: AdbServerConnectionOptions = { unref: false }
): Promise<ReadableWritablePair<Uint8Array, Uint8Array>> {
const socket = new Socket();
@ -81,7 +81,7 @@ export class AdbServerNodeTcpConnection implements AdbServerConnection {
return nodeSocketToStreamPair(socket);
}
public async addReverseTunnel(
async addReverseTunnel(
handler: AdbIncomingSocketHandler,
address?: string
): Promise<string> {
@ -127,23 +127,23 @@ export class AdbServerNodeTcpConnection implements AdbServerConnection {
address = `tcp:${info.address}:${info.port}`;
}
this._listeners.set(address, server);
this.#listeners.set(address, server);
return address;
}
removeReverseTunnel(address: string): ValueOrPromise<void> {
const server = this._listeners.get(address);
const server = this.#listeners.get(address);
if (!server) {
return;
}
server.close();
this._listeners.delete(address);
this.#listeners.delete(address);
}
clearReverseTunnels(): ValueOrPromise<void> {
for (const server of this._listeners.values()) {
for (const server of this.#listeners.values()) {
server.close();
}
this._listeners.clear();
this.#listeners.clear();
}
}

View file

@ -51,30 +51,30 @@ export interface AdbTransport extends Closeable {
}
export class Adb implements Closeable {
public readonly transport: AdbTransport;
readonly transport: AdbTransport;
public get serial() {
get serial() {
return this.transport.serial;
}
public get maxPayloadSize() {
get maxPayloadSize() {
return this.transport.maxPayloadSize;
}
public get banner() {
get banner() {
return this.transport.banner;
}
public get disconnected() {
get disconnected() {
return this.transport.disconnected;
}
public readonly subprocess: AdbSubprocess;
public readonly power: AdbPower;
public readonly reverse: AdbReverseCommand;
public readonly tcpip: AdbTcpIpCommand;
readonly subprocess: AdbSubprocess;
readonly power: AdbPower;
readonly reverse: AdbReverseCommand;
readonly tcpip: AdbTcpIpCommand;
public constructor(transport: AdbTransport) {
constructor(transport: AdbTransport) {
this.transport = transport;
this.subprocess = new AdbSubprocess(this);
@ -83,22 +83,22 @@ export class Adb implements Closeable {
this.tcpip = new AdbTcpIpCommand(this);
}
public supportsFeature(feature: AdbFeature): boolean {
supportsFeature(feature: AdbFeature): boolean {
return this.banner.features.includes(feature);
}
public async createSocket(service: string): Promise<AdbSocket> {
async createSocket(service: string): Promise<AdbSocket> {
return this.transport.connect(service);
}
public async createSocketAndWait(service: string): Promise<string> {
async createSocketAndWait(service: string): Promise<string> {
const socket = await this.createSocket(service);
return await socket.readable
.pipeThrough(new DecodeUtf8Stream())
.pipeThrough(new ConcatStringStream());
}
public async getProp(key: string): Promise<string> {
async getProp(key: string): Promise<string> {
const stdout = await this.subprocess.spawnAndWaitLegacy([
"getprop",
key,
@ -106,7 +106,7 @@ export class Adb implements Closeable {
return stdout.trim();
}
public async rm(...filenames: string[]): Promise<string> {
async rm(...filenames: string[]): Promise<string> {
// https://android.googlesource.com/platform/packages/modules/adb/+/1a0fb8846d4e6b671c8aa7f137a8c21d7b248716/client/adb_install.cpp#984
const stdout = await this.subprocess.spawnAndWaitLegacy([
"rm",
@ -116,16 +116,16 @@ export class Adb implements Closeable {
return stdout;
}
public async sync(): Promise<AdbSync> {
async sync(): Promise<AdbSync> {
const socket = await this.createSocket("sync:");
return new AdbSync(this, socket);
}
public async framebuffer(): Promise<AdbFrameBuffer> {
async framebuffer(): Promise<AdbFrameBuffer> {
return framebuffer(this);
}
public async close(): Promise<void> {
async close(): Promise<void> {
await this.transport.close();
}
}

View file

@ -8,7 +8,7 @@ export enum AdbBannerKey {
}
export class AdbBanner {
public static parse(banner: string) {
static parse(banner: string) {
let product: string | undefined;
let model: string | undefined;
let device: string | undefined;
@ -49,26 +49,26 @@ export class AdbBanner {
}
#product: string | undefined;
public get product() {
get product() {
return this.#product;
}
#model: string | undefined;
public get model() {
get model() {
return this.#model;
}
#device: string | undefined;
public get device() {
get device() {
return this.#device;
}
#features: AdbFeature[] = [];
public get features() {
get features() {
return this.#features;
}
public constructor(
constructor(
product: string | undefined,
model: string | undefined,
device: string | undefined,

View file

@ -5,7 +5,7 @@ import type { Adb } from "../adb.js";
export class AdbCommandBase extends AutoDisposable {
protected adb: Adb;
public constructor(adb: Adb) {
constructor(adb: Adb) {
super();
this.adb = adb;
}

View file

@ -6,23 +6,23 @@
import { AdbCommandBase } from "./base.js";
export class AdbPower extends AdbCommandBase {
public reboot(mode = "") {
reboot(mode = "") {
return this.adb.createSocketAndWait(`reboot:${mode}`);
}
public bootloader() {
bootloader() {
return this.reboot("bootloader");
}
public fastboot() {
fastboot() {
return this.reboot("fastboot");
}
public recovery() {
recovery() {
return this.reboot("recovery");
}
public sideload() {
sideload() {
return this.reboot("sideload");
}
@ -31,15 +31,15 @@ export class AdbPower extends AdbCommandBase {
*
* Only works on some Qualcomm devices.
*/
public qualcommEdlMode() {
qualcommEdlMode() {
return this.reboot("edl");
}
public powerOff() {
powerOff() {
return this.adb.subprocess.spawnAndWaitLegacy(["reboot", "-p"]);
}
public powerButton(longPress = false) {
powerButton(longPress = false) {
return this.adb.subprocess.spawnAndWaitLegacy([
"input",
"keyevent",
@ -52,7 +52,7 @@ export class AdbPower extends AdbCommandBase {
*
* Only works on Samsung devices.
*/
public samsungOdin() {
samsungOdin() {
return this.reboot("download");
}
}

View file

@ -20,14 +20,14 @@ const AdbReverseStringResponse = new Struct()
.string("content", { lengthField: "length", lengthFieldRadix: 16 });
export class AdbReverseError extends Error {
public constructor(message: string) {
constructor(message: string) {
super(message);
Object.setPrototypeOf(this, new.target.prototype);
}
}
export class AdbReverseNotSupportedError extends AdbReverseError {
public constructor() {
constructor() {
super(
"ADB reverse tunnel is not supported on this device when connected wirelessly.",
);
@ -57,7 +57,7 @@ export class AdbReverseCommand extends AutoDisposable {
readonly #deviceAddressToLocalAddress = new Map<string, string>();
public constructor(adb: Adb) {
constructor(adb: Adb) {
super();
this.adb = adb;
@ -77,7 +77,7 @@ export class AdbReverseCommand extends AutoDisposable {
return stream;
}
public async list(): Promise<AdbForwardListener[]> {
async list(): Promise<AdbForwardListener[]> {
const stream = await this.createBufferedStream("reverse:list-forward");
const response = await AdbReverseStringResponse.deserialize(stream);
@ -99,7 +99,7 @@ export class AdbReverseCommand extends AutoDisposable {
* @param localAddress The address that listens on the local machine.
* @returns `tcp:{ACTUAL_LISTENING_PORT}`, If `deviceAddress` is `tcp:0`; otherwise, `deviceAddress`.
*/
public async addExternal(deviceAddress: string, localAddress: string) {
async addExternal(deviceAddress: string, localAddress: string) {
const stream = await this.sendRequest(
`reverse:forward:${deviceAddress};${localAddress}`,
);
@ -137,7 +137,7 @@ export class AdbReverseCommand extends AutoDisposable {
* @throws {AdbReverseNotSupportedError} If ADB reverse tunnel is not supported on this device when connected wirelessly.
* @throws {AdbReverseError} If ADB daemon returns an error.
*/
public async add(
async add(
deviceAddress: string,
handler: AdbIncomingSocketHandler,
localAddress?: string,
@ -157,7 +157,7 @@ export class AdbReverseCommand extends AutoDisposable {
}
}
public async remove(deviceAddress: string): Promise<void> {
async remove(deviceAddress: string): Promise<void> {
const localAddress =
this.#deviceAddressToLocalAddress.get(deviceAddress);
if (localAddress) {
@ -169,7 +169,7 @@ export class AdbReverseCommand extends AutoDisposable {
// No need to close the stream, device will close it
}
public async removeAll(): Promise<void> {
async removeAll(): Promise<void> {
await this.adb.transport.clearReverseTunnels();
this.#deviceAddressToLocalAddress.clear();

View file

@ -37,7 +37,7 @@ export interface AdbSubprocessWaitResult {
}
export class AdbSubprocess extends AdbCommandBase {
private async createProtocol(
async #createProtocol(
mode: "pty" | "raw",
command?: string | string[],
options?: Partial<AdbSubprocessOptions>,
@ -75,11 +75,11 @@ export class AdbSubprocess extends AdbCommandBase {
* @param options The options for creating the `AdbSubprocessProtocol`
* @returns A new `AdbSubprocessProtocol` instance connecting to the spawned process.
*/
public shell(
shell(
command?: string | string[],
options?: Partial<AdbSubprocessOptions>,
): Promise<AdbSubprocessProtocol> {
return this.createProtocol("pty", command, options);
return this.#createProtocol("pty", command, options);
}
/**
@ -91,11 +91,11 @@ export class AdbSubprocess extends AdbCommandBase {
* @param options The options for creating the `AdbSubprocessProtocol`
* @returns A new `AdbSubprocessProtocol` instance connecting to the spawned process.
*/
public spawn(
spawn(
command: string | string[],
options?: Partial<AdbSubprocessOptions>,
): Promise<AdbSubprocessProtocol> {
return this.createProtocol("raw", command, options);
return this.#createProtocol("raw", command, options);
}
/**
@ -104,7 +104,7 @@ export class AdbSubprocess extends AdbCommandBase {
* @param options The options for creating the `AdbSubprocessProtocol`
* @returns The entire output of the command
*/
public async spawnAndWait(
async spawnAndWait(
command: string | string[],
options?: Partial<AdbSubprocessOptions>,
): Promise<AdbSubprocessWaitResult> {
@ -132,9 +132,7 @@ export class AdbSubprocess extends AdbCommandBase {
* @param command The command to run
* @returns The entire output of the command
*/
public async spawnAndWaitLegacy(
command: string | string[],
): Promise<string> {
async spawnAndWaitLegacy(command: string | string[]): Promise<string> {
const { stdout } = await this.spawnAndWait(command, {
protocols: [AdbSubprocessNoneProtocol],
});

View file

@ -13,17 +13,17 @@ import type { AdbSubprocessProtocol } from "./types.js";
* * `resize`: No
*/
export class AdbSubprocessNoneProtocol implements AdbSubprocessProtocol {
public static isSupported() {
static isSupported() {
return true;
}
public static async pty(adb: Adb, command: string) {
static async pty(adb: Adb, command: string) {
return new AdbSubprocessNoneProtocol(
await adb.createSocket(`shell:${command}`),
);
}
public static async raw(adb: Adb, command: string) {
static async raw(adb: Adb, command: string) {
// `shell,raw:${command}` also triggers raw mode,
// But is not supported on Android version <7.
return new AdbSubprocessNoneProtocol(
@ -36,7 +36,7 @@ export class AdbSubprocessNoneProtocol implements AdbSubprocessProtocol {
readonly #duplex: DuplexStreamFactory<Uint8Array, Uint8Array>;
// Legacy shell forwards all data to stdin.
public get stdin() {
get stdin() {
return this.#socket.writable;
}
@ -44,7 +44,7 @@ export class AdbSubprocessNoneProtocol implements AdbSubprocessProtocol {
/**
* Legacy shell mixes stdout and stderr.
*/
public get stdout() {
get stdout() {
return this.#stdout;
}
@ -52,16 +52,16 @@ export class AdbSubprocessNoneProtocol implements AdbSubprocessProtocol {
/**
* `stderr` will always be empty.
*/
public get stderr() {
get stderr() {
return this.#stderr;
}
#exit: Promise<number>;
public get exit() {
get exit() {
return this.#exit;
}
public constructor(socket: AdbSocket) {
constructor(socket: AdbSocket) {
this.#socket = socket;
// Link `stdout`, `stderr` and `stdin` together,
@ -77,11 +77,11 @@ export class AdbSubprocessNoneProtocol implements AdbSubprocessProtocol {
this.#exit = this.#duplex.closed.then(() => 0);
}
public resize() {
resize() {
// Not supported, but don't throw.
}
public kill() {
kill() {
return this.#duplex.close();
}
}

View file

@ -61,38 +61,38 @@ class StdinSerializeStream extends ConsumableTransformStream<
}
class MultiplexStream<T> {
private _readable: PushReadableStream<T>;
private _readableController!: PushReadableStreamController<T>;
public get readable() {
return this._readable;
#readable: PushReadableStream<T>;
#readableController!: PushReadableStreamController<T>;
get readable() {
return this.#readable;
}
private _activeCount = 0;
#activeCount = 0;
constructor() {
this._readable = new PushReadableStream((controller) => {
this._readableController = controller;
this.#readable = new PushReadableStream((controller) => {
this.#readableController = controller;
});
}
public createWriteable() {
createWriteable() {
return new WritableStream<T>({
start: () => {
this._activeCount += 1;
this.#activeCount += 1;
},
write: async (chunk) => {
await this._readableController.enqueue(chunk);
await this.#readableController.enqueue(chunk);
},
abort: () => {
this._activeCount -= 1;
if (this._activeCount === 0) {
this._readableController.close();
this.#activeCount -= 1;
if (this.#activeCount === 0) {
this.#readableController.close();
}
},
close: () => {
this._activeCount -= 1;
if (this._activeCount === 0) {
this._readableController.close();
this.#activeCount -= 1;
if (this.#activeCount === 0) {
this.#readableController.close();
}
},
});
@ -108,18 +108,18 @@ class MultiplexStream<T> {
* * `resize`: Yes
*/
export class AdbSubprocessShellProtocol implements AdbSubprocessProtocol {
public static isSupported(adb: Adb) {
static isSupported(adb: Adb) {
return adb.supportsFeature(AdbFeature.ShellV2);
}
public static async pty(adb: Adb, command: string) {
static async pty(adb: Adb, command: string) {
// TODO: AdbShellSubprocessProtocol: Support setting `XTERM` environment variable
return new AdbSubprocessShellProtocol(
await adb.createSocket(`shell,v2,pty:${command}`),
);
}
public static async raw(adb: Adb, command: string) {
static async raw(adb: Adb, command: string) {
return new AdbSubprocessShellProtocol(
await adb.createSocket(`shell,v2,raw:${command}`),
);
@ -131,26 +131,26 @@ export class AdbSubprocessShellProtocol implements AdbSubprocessProtocol {
>;
#stdin: WritableStream<Consumable<Uint8Array>>;
public get stdin() {
get stdin() {
return this.#stdin;
}
#stdout: ReadableStream<Uint8Array>;
public get stdout() {
get stdout() {
return this.#stdout;
}
#stderr: ReadableStream<Uint8Array>;
public get stderr() {
get stderr() {
return this.#stderr;
}
readonly #exit = new PromiseResolver<number>();
public get exit() {
get exit() {
return this.#exit.promise;
}
public constructor(socket: AdbSocket) {
constructor(socket: AdbSocket) {
this.#socket = socket;
// Check this image to help you understand the stream graph
@ -225,7 +225,7 @@ export class AdbSubprocessShellProtocol implements AdbSubprocessProtocol {
this.#socketWriter = multiplexer.createWriteable().getWriter();
}
public async resize(rows: number, cols: number) {
async resize(rows: number, cols: number) {
await ConsumableWritableStream.write(this.#socketWriter, {
id: AdbShellProtocolId.WindowSizeChange,
data: encodeUtf8(
@ -237,7 +237,7 @@ export class AdbSubprocessShellProtocol implements AdbSubprocessProtocol {
});
}
public kill() {
kill() {
return this.#socket.close();
}
}

View file

@ -19,11 +19,11 @@ export class AdbSyncSocketLocked implements AsyncExactReadable {
readonly #writeLock = new AutoResetEvent();
readonly #combiner: BufferCombiner;
public get position() {
get position() {
return this.#readable.position;
}
public constructor(
constructor(
writer: WritableStreamDefaultWriter<Consumable<Uint8Array>>,
readable: BufferedReadableStream,
bufferSize: number,
@ -35,39 +35,39 @@ export class AdbSyncSocketLocked implements AsyncExactReadable {
this.#combiner = new BufferCombiner(bufferSize);
}
private async writeInnerStream(buffer: Uint8Array) {
async #writeInnerStream(buffer: Uint8Array) {
await ConsumableWritableStream.write(this.#writer, buffer);
}
public async flush() {
async flush() {
try {
await this.#writeLock.wait();
const buffer = this.#combiner.flush();
if (buffer) {
await this.writeInnerStream(buffer);
await this.#writeInnerStream(buffer);
}
} finally {
this.#writeLock.notifyOne();
}
}
public async write(data: Uint8Array) {
async write(data: Uint8Array) {
try {
await this.#writeLock.wait();
for (const buffer of this.#combiner.push(data)) {
await this.writeInnerStream(buffer);
await this.#writeInnerStream(buffer);
}
} finally {
this.#writeLock.notifyOne();
}
}
public async readExactly(length: number) {
async readExactly(length: number) {
await this.flush();
return await this.#readable.readExactly(length);
}
public release(): void {
release(): void {
this.#combiner.flush();
this.#socketLock.notifyOne();
}
@ -78,7 +78,7 @@ export class AdbSyncSocket {
readonly #socket: AdbSocket;
readonly #locked: AdbSyncSocketLocked;
public constructor(socket: AdbSocket, bufferSize: number) {
constructor(socket: AdbSocket, bufferSize: number) {
this.#socket = socket;
this.#locked = new AdbSyncSocketLocked(
socket.writable.getWriter(),
@ -88,12 +88,12 @@ export class AdbSyncSocket {
);
}
public async lock() {
async lock() {
await this.#lock.wait();
return this.#locked;
}
public async close() {
async close() {
await this.#socket.close();
}
}

View file

@ -48,27 +48,27 @@ export class AdbSync extends AutoDisposable {
readonly #supportsSendReceiveV2: boolean;
readonly #needPushMkdirWorkaround: boolean;
public get supportsStat(): boolean {
get supportsStat(): boolean {
return this.#supportsStat;
}
public get supportsListV2(): boolean {
get supportsListV2(): boolean {
return this.#supportsListV2;
}
public get fixedPushMkdir(): boolean {
get fixedPushMkdir(): boolean {
return this.#fixedPushMkdir;
}
public get supportsSendReceiveV2(): boolean {
get supportsSendReceiveV2(): boolean {
return this.#supportsSendReceiveV2;
}
public get needPushMkdirWorkaround(): boolean {
get needPushMkdirWorkaround(): boolean {
return this.#needPushMkdirWorkaround;
}
public constructor(adb: Adb, socket: AdbSocket) {
constructor(adb: Adb, socket: AdbSocket) {
super();
this._adb = adb;
@ -86,11 +86,11 @@ export class AdbSync extends AutoDisposable {
!this.fixedPushMkdir;
}
public async lstat(path: string): Promise<AdbSyncStat> {
async lstat(path: string): Promise<AdbSyncStat> {
return await adbSyncLstat(this._socket, path, this.supportsStat);
}
public async stat(path: string) {
async stat(path: string) {
if (!this.supportsStat) {
throw new Error("Not supported");
}
@ -98,7 +98,7 @@ export class AdbSync extends AutoDisposable {
return await adbSyncStat(this._socket, path);
}
public async isDirectory(path: string): Promise<boolean> {
async isDirectory(path: string): Promise<boolean> {
try {
await this.lstat(path + "/");
return true;
@ -107,11 +107,11 @@ export class AdbSync extends AutoDisposable {
}
}
public opendir(path: string): AsyncGenerator<AdbSyncEntry, void, void> {
opendir(path: string): AsyncGenerator<AdbSyncEntry, void, void> {
return adbSyncOpenDir(this._socket, path, this.supportsListV2);
}
public async readdir(path: string) {
async readdir(path: string) {
const results: AdbSyncEntry[] = [];
for await (const entry of this.opendir(path)) {
results.push(entry);
@ -125,7 +125,7 @@ export class AdbSync extends AutoDisposable {
* @param filename The full path of the file on device to read.
* @returns A `ReadableStream` that contains the file content.
*/
public read(filename: string): ReadableStream<Uint8Array> {
read(filename: string): ReadableStream<Uint8Array> {
return adbSyncPull(this._socket, filename);
}
@ -134,7 +134,7 @@ export class AdbSync extends AutoDisposable {
*
* @param options The content and options of the file to write.
*/
public async write(options: AdbSyncWriteOptions): Promise<void> {
async write(options: AdbSyncWriteOptions): Promise<void> {
if (this.needPushMkdirWorkaround) {
// It may fail if the path is already existed.
// Ignore the result.
@ -153,7 +153,7 @@ export class AdbSync extends AutoDisposable {
});
}
public override async dispose() {
override async dispose() {
super.dispose();
await this._socket.close();
}

View file

@ -1,7 +1,7 @@
import { AdbCommandBase } from "./base.js";
export class AdbTcpIpCommand extends AdbCommandBase {
public async setPort(port: number): Promise<string> {
async setPort(port: number): Promise<string> {
if (port <= 0) {
throw new Error(`Invalid port ${port}`);
}
@ -13,7 +13,7 @@ export class AdbTcpIpCommand extends AdbCommandBase {
return output;
}
public async disable(): Promise<string> {
async disable(): Promise<string> {
const output = await this.adb.createSocketAndWait("usb:");
if (output !== "restarting in USB mode\n") {
throw new Error(output);

View file

@ -120,34 +120,30 @@ export const ADB_DEFAULT_AUTHENTICATORS: AdbAuthenticator[] = [
];
export class AdbAuthenticationProcessor implements Disposable {
public readonly authenticators: readonly AdbAuthenticator[];
readonly authenticators: readonly AdbAuthenticator[];
private readonly credentialStore: AdbCredentialStore;
readonly #credentialStore: AdbCredentialStore;
#pendingRequest = new PromiseResolver<AdbPacketData>();
#iterator: AsyncIterator<AdbPacketData, void, void> | undefined;
public constructor(
constructor(
authenticators: readonly AdbAuthenticator[],
credentialStore: AdbCredentialStore,
) {
this.authenticators = authenticators;
this.credentialStore = credentialStore;
this.#credentialStore = credentialStore;
}
private getNextRequest = (): Promise<AdbPacketData> => {
#getNextRequest = (): Promise<AdbPacketData> => {
return this.#pendingRequest.promise;
};
private async *invokeAuthenticator(): AsyncGenerator<
AdbPacketData,
void,
void
> {
async *#invokeAuthenticator(): AsyncGenerator<AdbPacketData, void, void> {
for (const authenticator of this.authenticators) {
for await (const packet of authenticator(
this.credentialStore,
this.getNextRequest,
this.#credentialStore,
this.#getNextRequest,
)) {
// If the authenticator yielded a response
// Prepare `nextRequest` for next authentication request
@ -162,9 +158,9 @@ export class AdbAuthenticationProcessor implements Disposable {
}
}
public async process(packet: AdbPacketData): Promise<AdbPacketData> {
async process(packet: AdbPacketData): Promise<AdbPacketData> {
if (!this.#iterator) {
this.#iterator = this.invokeAuthenticator();
this.#iterator = this.#invokeAuthenticator();
}
this.#pendingRequest.resolve(packet);
@ -177,7 +173,7 @@ export class AdbAuthenticationProcessor implements Disposable {
return result.value;
}
public dispose() {
dispose() {
void this.#iterator?.return?.();
}
}

View file

@ -51,18 +51,18 @@ export class AdbPacketDispatcher implements Closeable {
#writer: WritableStreamDefaultWriter<Consumable<AdbPacketInit>>;
public readonly options: AdbPacketDispatcherOptions;
readonly options: AdbPacketDispatcherOptions;
#closed = false;
#disconnected = new PromiseResolver<void>();
public get disconnected() {
get disconnected() {
return this.#disconnected.promise;
}
#incomingSocketHandlers = new Map<string, AdbIncomingSocketHandler>();
#readAbortController = new AbortController();
public constructor(
constructor(
connection: ReadableWritablePair<
AdbPacketData,
Consumable<AdbPacketInit>
@ -77,10 +77,10 @@ export class AdbPacketDispatcher implements Closeable {
write: async (packet) => {
switch (packet.command) {
case AdbCommand.OK:
this.handleOk(packet);
this.#handleOk(packet);
break;
case AdbCommand.Close:
await this.handleClose(packet);
await this.#handleClose(packet);
break;
case AdbCommand.Write:
if (this.#sockets.has(packet.arg1)) {
@ -98,7 +98,7 @@ export class AdbPacketDispatcher implements Closeable {
`Unknown local socket id: ${packet.arg1}`,
);
case AdbCommand.Open:
await this.handleOpen(packet);
await this.#handleOpen(packet);
break;
default:
// Junk data may only appear in the authentication phase,
@ -125,20 +125,20 @@ export class AdbPacketDispatcher implements Closeable {
)
.then(
() => {
this.dispose();
this.#dispose();
},
(e) => {
if (!this.#closed) {
this.#disconnected.reject(e);
}
this.dispose();
this.#dispose();
},
);
this.#writer = connection.writable.getWriter();
}
private handleOk(packet: AdbPacketData) {
#handleOk(packet: AdbPacketData) {
if (this.#initializers.resolve(packet.arg1, packet.arg0)) {
// Device successfully created the socket
return;
@ -156,7 +156,7 @@ export class AdbPacketDispatcher implements Closeable {
void this.sendPacket(AdbCommand.Close, packet.arg1, packet.arg0);
}
private async handleClose(packet: AdbPacketData) {
async #handleClose(packet: AdbPacketData) {
// If the socket is still pending
if (
packet.arg0 === 0 &&
@ -201,22 +201,19 @@ export class AdbPacketDispatcher implements Closeable {
// the device may also respond with two `CLSE` packets.
}
public addReverseTunnel(
service: string,
handler: AdbIncomingSocketHandler,
) {
addReverseTunnel(service: string, handler: AdbIncomingSocketHandler) {
this.#incomingSocketHandlers.set(service, handler);
}
public removeReverseTunnel(address: string) {
removeReverseTunnel(address: string) {
this.#incomingSocketHandlers.delete(address);
}
public clearReverseTunnels() {
clearReverseTunnels() {
this.#incomingSocketHandlers.clear();
}
private async handleOpen(packet: AdbPacketData) {
async #handleOpen(packet: AdbPacketData) {
// `AsyncOperationManager` doesn't support skipping IDs
// Use `add` + `resolve` to simulate this behavior
const [localId] = this.#initializers.add<number>();
@ -251,7 +248,7 @@ export class AdbPacketDispatcher implements Closeable {
}
}
public async createSocket(service: string): Promise<AdbSocket> {
async createSocket(service: string): Promise<AdbSocket> {
if (this.options.appendNullToServiceString) {
service += "\0";
}
@ -273,7 +270,7 @@ export class AdbPacketDispatcher implements Closeable {
return controller.socket;
}
public async sendPacket(
async sendPacket(
command: AdbCommand,
arg0: number,
arg1: number,
@ -299,7 +296,7 @@ export class AdbPacketDispatcher implements Closeable {
});
}
public async close() {
async close() {
// Send `CLSE` packets for all sockets
await Promise.all(
Array.from(this.#sockets.values(), (socket) => socket.close()),
@ -315,7 +312,7 @@ export class AdbPacketDispatcher implements Closeable {
// `pipe().then()` will call `dispose`
}
private dispose() {
#dispose() {
for (const socket of this.#sockets.values()) {
socket.dispose().catch(unreachable);
}

View file

@ -53,7 +53,7 @@ export class AdbPacketSerializeStream extends ConsumableTransformStream<
AdbPacketInit,
Uint8Array
> {
public constructor() {
constructor() {
const headerBuffer = new Uint8Array(AdbPacketHeader.size);
super({
transform: async (chunk, controller) => {

View file

@ -42,23 +42,23 @@ export class AdbDaemonSocketController
Closeable,
Disposable
{
private readonly dispatcher!: AdbPacketDispatcher;
readonly #dispatcher!: AdbPacketDispatcher;
public readonly localId!: number;
public readonly remoteId!: number;
public readonly localCreated!: boolean;
public readonly service!: string;
readonly localId!: number;
readonly remoteId!: number;
readonly localCreated!: boolean;
readonly service!: string;
#duplex: DuplexStreamFactory<Uint8Array, Consumable<Uint8Array>>;
#readable: ReadableStream<Uint8Array>;
#readableController!: PushReadableStreamController<Uint8Array>;
public get readable() {
get readable() {
return this.#readable;
}
#writePromise: PromiseResolver<void> | undefined;
public readonly writable: WritableStream<Consumable<Uint8Array>>;
readonly writable: WritableStream<Consumable<Uint8Array>>;
#closed = false;
/**
@ -66,17 +66,21 @@ export class AdbDaemonSocketController
*
* It's only used by dispatcher to avoid sending another `CLSE` packet to remote.
*/
public get closed() {
get closed() {
return this.#closed;
}
private _socket: AdbDaemonSocket;
public get socket() {
return this._socket;
#socket: AdbDaemonSocket;
get socket() {
return this.#socket;
}
public constructor(options: AdbDaemonSocketConstructionOptions) {
Object.assign(this, options);
constructor(options: AdbDaemonSocketConstructionOptions) {
this.#dispatcher = options.dispatcher;
this.localId = options.localId;
this.remoteId = options.remoteId;
this.localCreated = options.localCreated;
this.service = options.service;
// Check this image to help you understand the stream graph
// cspell: disable-next-line
@ -89,10 +93,10 @@ export class AdbDaemonSocketController
close: async () => {
this.#closed = true;
await this.dispatcher.sendPacket(
await this.#dispatcher.sendPacket(
AdbCommand.Close,
this.localId,
this.remoteId,
this.remoteId
);
// Don't `dispose` here, we need to wait for `CLSE` response packet.
@ -114,8 +118,8 @@ export class AdbDaemonSocketController
size(chunk) {
return chunk.byteLength;
},
},
),
}
)
);
this.writable = pipeFrom(
@ -124,23 +128,23 @@ export class AdbDaemonSocketController
write: async (chunk) => {
// Wait for an ack packet
this.#writePromise = new PromiseResolver();
await this.dispatcher.sendPacket(
await this.#dispatcher.sendPacket(
AdbCommand.Write,
this.localId,
this.remoteId,
chunk,
chunk
);
await this.#writePromise.promise;
},
}),
})
),
new DistributionStream(this.dispatcher.options.maxPayloadSize),
new DistributionStream(this.#dispatcher.options.maxPayloadSize)
);
this._socket = new AdbDaemonSocket(this);
this.#socket = new AdbDaemonSocket(this);
}
public async enqueue(data: Uint8Array) {
async enqueue(data: Uint8Array) {
// Consumer may abort the `ReadableStream` to close the socket,
// it's OK to throw away further packets in this case.
if (this.#readableController.abortSignal.aborted) {
@ -150,15 +154,15 @@ export class AdbDaemonSocketController
await this.#readableController.enqueue(data);
}
public ack() {
ack() {
this.#writePromise?.resolve();
}
public async close(): Promise<void> {
async close(): Promise<void> {
await this.#duplex.close();
}
public dispose() {
dispose() {
return this.#duplex.dispose();
}
}
@ -178,35 +182,35 @@ export class AdbDaemonSocket
{
#controller: AdbDaemonSocketController;
public get localId(): number {
get localId(): number {
return this.#controller.localId;
}
public get remoteId(): number {
get remoteId(): number {
return this.#controller.remoteId;
}
public get localCreated(): boolean {
get localCreated(): boolean {
return this.#controller.localCreated;
}
public get service(): string {
get service(): string {
return this.#controller.service;
}
public get readable(): ReadableStream<Uint8Array> {
get readable(): ReadableStream<Uint8Array> {
return this.#controller.readable;
}
public get writable(): WritableStream<Consumable<Uint8Array>> {
get writable(): WritableStream<Consumable<Uint8Array>> {
return this.#controller.writable;
}
public get closed(): boolean {
get closed(): boolean {
return this.#controller.closed;
}
public constructor(controller: AdbDaemonSocketController) {
constructor(controller: AdbDaemonSocketController) {
this.#controller = controller;
}
public close() {
close() {
return this.#controller.close();
}
}

View file

@ -51,7 +51,7 @@ export class AdbDaemonTransport implements AdbTransport {
* on the same connection. Because every time the device receives a `CNXN` packet,
* it resets all internal state, and starts a new authentication process.
*/
public static async authenticate({
static async authenticate({
serial,
connection,
credentialStore,
@ -186,30 +186,30 @@ export class AdbDaemonTransport implements AdbTransport {
readonly #dispatcher: AdbPacketDispatcher;
#serial: string;
public get serial() {
get serial() {
return this.#serial;
}
#protocolVersion: number;
public get protocolVersion() {
get protocolVersion() {
return this.#protocolVersion;
}
#maxPayloadSize: number;
public get maxPayloadSize() {
get maxPayloadSize() {
return this.#maxPayloadSize;
}
#banner: AdbBanner;
public get banner() {
get banner() {
return this.#banner;
}
public get disconnected() {
get disconnected() {
return this.#dispatcher.disconnected;
}
public constructor({
constructor({
serial,
connection,
version,
@ -239,11 +239,11 @@ export class AdbDaemonTransport implements AdbTransport {
this.#maxPayloadSize = maxPayloadSize;
}
public connect(service: string): ValueOrPromise<AdbSocket> {
connect(service: string): ValueOrPromise<AdbSocket> {
return this.#dispatcher.createSocket(service);
}
public addReverseTunnel(
addReverseTunnel(
handler: AdbIncomingSocketHandler,
address?: string,
): string {
@ -255,15 +255,15 @@ export class AdbDaemonTransport implements AdbTransport {
return address;
}
public removeReverseTunnel(address: string): void {
removeReverseTunnel(address: string): void {
this.#dispatcher.removeReverseTunnel(address);
}
public clearReverseTunnels(): void {
clearReverseTunnels(): void {
this.#dispatcher.clearReverseTunnels();
}
public close(): ValueOrPromise<void> {
close(): ValueOrPromise<void> {
return this.#dispatcher.close();
}
}

View file

@ -74,17 +74,17 @@ export interface AdbServerDevice {
}
export class AdbServerClient {
public static readonly VERSION = 41;
static readonly VERSION = 41;
public readonly connection: AdbServerConnection;
readonly connection: AdbServerConnection;
public constructor(connection: AdbServerConnection) {
constructor(connection: AdbServerConnection) {
this.connection = connection;
}
public static readString(stream: ExactReadable): string;
public static readString(stream: AsyncExactReadable): PromiseLike<string>;
public static readString(
static readString(stream: ExactReadable): string;
static readString(stream: AsyncExactReadable): PromiseLike<string>;
static readString(
stream: ExactReadable | AsyncExactReadable,
): string | PromiseLike<string> {
return SyncPromise.try(() => stream.readExactly(4))
@ -98,7 +98,7 @@ export class AdbServerClient {
.valueOrPromise();
}
public static async writeString(
static async writeString(
writer: WritableStreamDefaultWriter<Uint8Array>,
value: string,
): Promise<void> {
@ -109,7 +109,7 @@ export class AdbServerClient {
await writer.write(buffer);
}
public static async readOkay(
static async readOkay(
stream: ExactReadable | AsyncExactReadable,
): Promise<void> {
const response = decodeUtf8(await stream.readExactly(4));
@ -125,7 +125,7 @@ export class AdbServerClient {
throw new Error(`Unexpected response: ${response}`);
}
public async connect(
async connect(
request: string,
options?: AdbServerConnectionOptions,
): Promise<ReadableWritablePair<Uint8Array, Uint8Array>> {
@ -156,7 +156,7 @@ export class AdbServerClient {
}
}
public async getVersion(): Promise<number> {
async getVersion(): Promise<number> {
const connection = await this.connect("host:version");
const readable = new BufferedReadableStream(connection.readable);
try {
@ -169,7 +169,7 @@ export class AdbServerClient {
}
}
public async validateVersion() {
async validateVersion() {
const version = await this.getVersion();
if (version !== AdbServerClient.VERSION) {
throw new Error(
@ -178,13 +178,13 @@ export class AdbServerClient {
}
}
public async killServer(): Promise<void> {
async killServer(): Promise<void> {
const connection = await this.connect("host:kill");
connection.writable.close().catch(NOOP);
connection.readable.cancel().catch(NOOP);
}
public async getServerFeatures(): Promise<AdbFeature[]> {
async getServerFeatures(): Promise<AdbFeature[]> {
const connection = await this.connect("host:host-features");
const readable = new BufferedReadableStream(connection.readable);
try {
@ -196,7 +196,7 @@ export class AdbServerClient {
}
}
public async getDevices(): Promise<AdbServerDevice[]> {
async getDevices(): Promise<AdbServerDevice[]> {
const connection = await this.connect("host:devices-l");
const readable = new BufferedReadableStream(connection.readable);
try {
@ -253,10 +253,7 @@ export class AdbServerClient {
}
}
public formatDeviceService(
device: AdbServerDeviceSelector,
command: string,
) {
formatDeviceService(device: AdbServerDeviceSelector, command: string) {
if (!device) {
return `host:${command}`;
}
@ -282,7 +279,7 @@ export class AdbServerClient {
* @param device The device selector
* @returns The transport ID of the selected device, and the features supported by the device.
*/
public async getDeviceFeatures(
async getDeviceFeatures(
device: AdbServerDeviceSelector,
): Promise<{ transportId: bigint; features: AdbFeature[] }> {
// Usually the client sends a device command using `connectDevice`,
@ -309,7 +306,7 @@ export class AdbServerClient {
* @param service The service to forward
* @returns An `AdbServerSocket` that can be used to communicate with the service
*/
public async connectDevice(
async connectDevice(
device: AdbServerDeviceSelector,
service: string,
): Promise<AdbServerSocket> {
@ -386,7 +383,7 @@ export class AdbServerClient {
* @param options The options
* @returns A promise that resolves when the condition is met.
*/
public async waitFor(
async waitFor(
device: AdbServerDeviceSelector,
state: "device" | "disconnect",
options?: AdbServerConnectionOptions,
@ -418,7 +415,7 @@ export class AdbServerClient {
await this.connect(service, options);
}
public async createTransport(
async createTransport(
device: AdbServerDeviceSelector,
): Promise<AdbServerTransport> {
const { transportId, features } = await this.getDeviceFeatures(device);

View file

@ -14,19 +14,19 @@ import type { AdbServerClient } from "./client.js";
export class AdbServerTransport implements AdbTransport {
#client: AdbServerClient;
public readonly serial: string;
readonly serial: string;
public readonly transportId: bigint;
readonly transportId: bigint;
public readonly maxPayloadSize: number = 1 * 1024 * 1024;
readonly maxPayloadSize: number = 1 * 1024 * 1024;
public readonly banner: AdbBanner;
readonly banner: AdbBanner;
#closed = new PromiseResolver<void>();
#waitAbortController = new AbortController();
public readonly disconnected: Promise<void>;
readonly disconnected: Promise<void>;
public constructor(
constructor(
client: AdbServerClient,
serial: string,
banner: AdbBanner,
@ -46,7 +46,7 @@ export class AdbServerTransport implements AdbTransport {
]);
}
public async connect(service: string): Promise<AdbSocket> {
async connect(service: string): Promise<AdbSocket> {
return await this.#client.connectDevice(
{
transportId: this.transportId,
@ -55,18 +55,18 @@ export class AdbServerTransport implements AdbTransport {
);
}
public async addReverseTunnel(
async addReverseTunnel(
handler: AdbIncomingSocketHandler,
address?: string,
): Promise<string> {
return await this.#client.connection.addReverseTunnel(handler, address);
}
public async removeReverseTunnel(address: string): Promise<void> {
async removeReverseTunnel(address: string): Promise<void> {
await this.#client.connection.removeReverseTunnel(address);
}
public async clearReverseTunnels(): Promise<void> {
async clearReverseTunnels(): Promise<void> {
await this.#client.connection.clearReverseTunnels();
}

View file

@ -5,11 +5,11 @@ export class AutoResetEvent implements Disposable {
#set: boolean;
readonly #queue: PromiseResolver<void>[] = [];
public constructor(initialSet = false) {
constructor(initialSet = false) {
this.#set = initialSet;
}
public wait(): Promise<void> {
wait(): Promise<void> {
if (!this.#set) {
this.#set = true;
@ -23,7 +23,7 @@ export class AutoResetEvent implements Disposable {
return resolver.promise;
}
public notifyOne() {
notifyOne() {
if (this.#queue.length !== 0) {
this.#queue.pop()!.resolve();
} else {
@ -31,7 +31,7 @@ export class AutoResetEvent implements Disposable {
}
}
public dispose() {
dispose() {
for (const item of this.#queue) {
item.reject(new Error("The AutoResetEvent has been disposed"));
}

View file

@ -10,7 +10,7 @@ export class ConditionalVariable implements Disposable {
#locked = false;
readonly #queue: WaitEntry[] = [];
public wait(condition: () => boolean): Promise<void> {
wait(condition: () => boolean): Promise<void> {
if (!this.#locked) {
this.#locked = true;
if (this.#queue.length === 0 && condition()) {
@ -23,7 +23,7 @@ export class ConditionalVariable implements Disposable {
return resolver.promise;
}
public notifyOne() {
notifyOne() {
const entry = this.#queue.shift();
if (entry) {
if (entry.condition()) {
@ -34,7 +34,7 @@ export class ConditionalVariable implements Disposable {
}
}
public dispose(): void {
dispose(): void {
for (const item of this.#queue) {
item.resolver.reject(
new Error("The ConditionalVariable has been disposed"),

View file

@ -21,7 +21,7 @@ export class AdbBackup extends AdbCommandBase {
/**
* User must confirm backup on device within 60 seconds.
*/
public async backup(
async backup(
options: AdbBackupOptions,
): Promise<ReadableStream<Uint8Array>> {
const args = ["bu", "backup"];
@ -62,7 +62,7 @@ export class AdbBackup extends AdbCommandBase {
* User must enter the password (if any) and
* confirm restore on device within 60 seconds.
*/
public async restore(options: AdbRestoreOptions): Promise<void> {
async restore(options: AdbRestoreOptions): Promise<void> {
const args = ["bu", "restore"];
if (options.user !== undefined) {
args.push("--user", options.user.toString());

View file

@ -20,22 +20,22 @@ export interface BugReportZVersion {
}
export class BugReportZ extends AdbCommandBase {
public static VERSION_REGEX = /(\d+)\.(\d+)/;
static VERSION_REGEX = /(\d+)\.(\d+)/;
public static BEGIN_REGEX = /BEGIN:(.*)/;
static BEGIN_REGEX = /BEGIN:(.*)/;
public static PROGRESS_REGEX = /PROGRESS:(.*)\/(.*)/;
static PROGRESS_REGEX = /PROGRESS:(.*)\/(.*)/;
public static OK_REGEX = /OK:(.*)/;
static OK_REGEX = /OK:(.*)/;
public static FAIL_REGEX = /FAIL:(.*)/;
static FAIL_REGEX = /FAIL:(.*)/;
/**
* Retrieve the version of bugreportz.
*
* @returns a `BugReportVersion` object, or `undefined` if `bugreportz` is not available.
*/
public async version(): Promise<BugReportZVersion | undefined> {
async version(): Promise<BugReportZVersion | undefined> {
// bugreportz requires shell protocol
if (!AdbSubprocessShellProtocol.isSupported(this.adb)) {
return undefined;
@ -65,7 +65,7 @@ export class BugReportZ extends AdbCommandBase {
};
}
public supportProgress(major: number, minor: number): boolean {
supportProgress(major: number, minor: number): boolean {
return major > 1 || minor >= 1;
}
@ -78,7 +78,7 @@ export class BugReportZ extends AdbCommandBase {
* @param onProgress Progress callback. Only specify this if `supportsProgress` is `true`.
* @returns The path of the bugreport file.
*/
public async generate(
async generate(
onProgress?: (progress: string, total: string) => void,
): Promise<string> {
const process = await this.adb.subprocess.spawn([
@ -133,11 +133,11 @@ export class BugReportZ extends AdbCommandBase {
return filename;
}
public supportStream(major: number, minor: number): boolean {
supportStream(major: number, minor: number): boolean {
return major > 1 || minor >= 2;
}
public stream(): ReadableStream<Uint8Array> {
stream(): ReadableStream<Uint8Array> {
return new PushReadableStream(async (controller) => {
const process = await this.adb.subprocess.spawn([
"bugreportz",
@ -173,7 +173,7 @@ export class BugReportZ extends AdbCommandBase {
// https://cs.android.com/android/platform/superproject/+/master:frameworks/native/cmds/bugreport/bugreport.cpp;drc=9b73bf07d73dbab5b792632e1e233edbad77f5fd;bpv=0;bpt=0
export class BugReport extends AdbCommandBase {
public generate(): ReadableStream<Uint8Array> {
generate(): ReadableStream<Uint8Array> {
return new WrapReadableStream(async () => {
const process = await this.adb.subprocess.spawn(["bugreport"]);
return process.stdout;

View file

@ -14,26 +14,26 @@ import { ConcatStringStream, DecodeUtf8Stream } from "@yume-chan/stream-extra";
export class Cmd extends AdbCommandBase {
#supportsShellV2: boolean;
public get supportsShellV2() {
get supportsShellV2() {
return this.#supportsShellV2;
}
#supportsCmd: boolean;
public get supportsCmd() {
get supportsCmd() {
return this.#supportsCmd;
}
#supportsAbb: boolean;
public get supportsAbb() {
get supportsAbb() {
return this.#supportsAbb;
}
#supportsAbbExec: boolean;
public get supportsAbbExec() {
get supportsAbbExec() {
return this.#supportsAbbExec;
}
public constructor(adb: Adb) {
constructor(adb: Adb) {
super(adb);
this.#supportsShellV2 = adb.supportsFeature(AdbFeature.ShellV2);
this.#supportsCmd = adb.supportsFeature(AdbFeature.Cmd);
@ -41,7 +41,7 @@ export class Cmd extends AdbCommandBase {
this.#supportsAbbExec = adb.supportsFeature(AdbFeature.AbbExec);
}
public async spawn(
async spawn(
shellProtocol: boolean,
command: string,
...args: string[]
@ -76,7 +76,7 @@ export class Cmd extends AdbCommandBase {
throw new Error("Not supported");
}
public async spawnAndWait(
async spawnAndWait(
command: string,
...args: string[]
): Promise<AdbSubprocessWaitResult> {

View file

@ -52,55 +52,63 @@ export const DemoModeStatusBarModes = [
export type DemoModeStatusBarMode = (typeof DemoModeStatusBarModes)[number];
export class DemoMode extends AdbCommandBase {
private settings: Settings;
#settings: Settings;
constructor(adb: Adb) {
super(adb);
this.settings = new Settings(adb);
this.#settings = new Settings(adb);
}
public static readonly AllowedSettingKey = "sysui_demo_allowed";
static readonly ALLOWED_SETTING_KEY = "sysui_demo_allowed";
// Demo Mode actually doesn't have a setting indicates its enablement
// However Developer Mode menu uses this key
// So we can only try our best to guess if it's enabled
public static readonly EnabledSettingKey = "sysui_tuner_demo_on";
static readonly ENABLED_SETTING_KEY = "sysui_tuner_demo_on";
public async getAllowed(): Promise<boolean> {
const output = await this.settings.get(
async getAllowed(): Promise<boolean> {
const output = await this.#settings.get(
"global",
DemoMode.AllowedSettingKey,
DemoMode.ALLOWED_SETTING_KEY,
);
return output === "1";
}
public async setAllowed(value: boolean): Promise<void> {
async setAllowed(value: boolean): Promise<void> {
if (value) {
await this.settings.put("global", DemoMode.AllowedSettingKey, "1");
await this.#settings.put(
"global",
DemoMode.ALLOWED_SETTING_KEY,
"1",
);
} else {
await this.setEnabled(false);
await this.settings.delete("global", DemoMode.AllowedSettingKey);
await this.#settings.delete("global", DemoMode.ALLOWED_SETTING_KEY);
}
}
public async getEnabled(): Promise<boolean> {
const result = await this.settings.get(
async getEnabled(): Promise<boolean> {
const result = await this.#settings.get(
"global",
DemoMode.EnabledSettingKey,
DemoMode.ENABLED_SETTING_KEY,
);
return result === "1";
}
public async setEnabled(value: boolean): Promise<void> {
async setEnabled(value: boolean): Promise<void> {
if (value) {
await this.settings.put("global", DemoMode.EnabledSettingKey, "1");
await this.#settings.put(
"global",
DemoMode.ENABLED_SETTING_KEY,
"1",
);
} else {
await this.settings.delete("global", DemoMode.EnabledSettingKey);
await this.#settings.delete("global", DemoMode.ENABLED_SETTING_KEY);
await this.broadcast("exit");
}
}
public async broadcast(
async broadcast(
command: string,
extra?: Record<string, string>,
): Promise<void> {
@ -122,31 +130,27 @@ export class DemoMode extends AdbCommandBase {
]);
}
public async setBatteryLevel(level: number): Promise<void> {
async setBatteryLevel(level: number): Promise<void> {
await this.broadcast("battery", { level: level.toString() });
}
public async setBatteryCharging(value: boolean): Promise<void> {
async setBatteryCharging(value: boolean): Promise<void> {
await this.broadcast("battery", { plugged: value.toString() });
}
public async setPowerSaveMode(value: boolean): Promise<void> {
async setPowerSaveMode(value: boolean): Promise<void> {
await this.broadcast("battery", { powersave: value.toString() });
}
public async setAirplaneMode(show: boolean): Promise<void> {
async setAirplaneMode(show: boolean): Promise<void> {
await this.broadcast("network", { airplane: show ? "show" : "hide" });
}
public async setWifiSignalStrength(
value: DemoModeSignalStrength,
): Promise<void> {
async setWifiSignalStrength(value: DemoModeSignalStrength): Promise<void> {
await this.broadcast("network", { wifi: "show", level: value });
}
public async setMobileDataType(
value: DemoModeMobileDataType,
): Promise<void> {
async setMobileDataType(value: DemoModeMobileDataType): Promise<void> {
for (let i = 0; i < 2; i += 1) {
await this.broadcast("network", {
mobile: "show",
@ -164,60 +168,60 @@ export class DemoMode extends AdbCommandBase {
}
}
public async setMobileSignalStrength(
async setMobileSignalStrength(
value: DemoModeSignalStrength,
): Promise<void> {
await this.broadcast("network", { mobile: "show", level: value });
}
public async setNoSimCardIcon(show: boolean): Promise<void> {
async setNoSimCardIcon(show: boolean): Promise<void> {
await this.broadcast("network", { nosim: show ? "show" : "hide" });
}
public async setStatusBarMode(mode: DemoModeStatusBarMode): Promise<void> {
async setStatusBarMode(mode: DemoModeStatusBarMode): Promise<void> {
await this.broadcast("bars", { mode });
}
public async setVibrateModeEnabled(value: boolean): Promise<void> {
async setVibrateModeEnabled(value: boolean): Promise<void> {
// https://cs.android.com/android/platform/superproject/+/master:frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java;l=103
await this.broadcast("status", { volume: value ? "vibrate" : "hide" });
}
public async setBluetoothConnected(value: boolean): Promise<void> {
async setBluetoothConnected(value: boolean): Promise<void> {
// https://cs.android.com/android/platform/superproject/+/master:frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java;l=114
await this.broadcast("status", {
bluetooth: value ? "connected" : "hide",
});
}
public async setLocatingIcon(show: boolean): Promise<void> {
async setLocatingIcon(show: boolean): Promise<void> {
await this.broadcast("status", { location: show ? "show" : "hide" });
}
public async setAlarmIcon(show: boolean): Promise<void> {
async setAlarmIcon(show: boolean): Promise<void> {
await this.broadcast("status", { alarm: show ? "show" : "hide" });
}
public async setSyncingIcon(show: boolean): Promise<void> {
async setSyncingIcon(show: boolean): Promise<void> {
await this.broadcast("status", { sync: show ? "show" : "hide" });
}
public async setMuteIcon(show: boolean): Promise<void> {
async setMuteIcon(show: boolean): Promise<void> {
await this.broadcast("status", { mute: show ? "show" : "hide" });
}
public async setSpeakerPhoneIcon(show: boolean): Promise<void> {
async setSpeakerPhoneIcon(show: boolean): Promise<void> {
await this.broadcast("status", {
speakerphone: show ? "show" : "hide",
});
}
public async setNotificationsVisibility(show: boolean): Promise<void> {
async setNotificationsVisibility(show: boolean): Promise<void> {
// https://cs.android.com/android/platform/superproject/+/master:frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java;l=3131
await this.broadcast("notifications", { visible: show.toString() });
}
public async setTime(hour: number, minute: number): Promise<void> {
async setTime(hour: number, minute: number): Promise<void> {
await this.broadcast("clock", {
// cspell: disable-next-line
hhmm:

View file

@ -1,7 +1,7 @@
import { AdbCommandBase } from "@yume-chan/adb";
export class DumpSys extends AdbCommandBase {
public async diskStats() {
async diskStats() {
const output = await this.adb.subprocess.spawnAndWaitLegacy([
"dumpsys",
"diskstats",
@ -34,7 +34,7 @@ export class DumpSys extends AdbCommandBase {
};
}
public async battery() {
async battery() {
const output = await this.adb.subprocess.spawnAndWaitLegacy([
"dumpsys",
"battery",

View file

@ -382,35 +382,35 @@ export interface LogSize {
}
export class Logcat extends AdbCommandBase {
public static logIdToName(id: LogId): string {
static logIdToName(id: LogId): string {
return LogId[id]!;
}
public static logNameToId(name: string): LogId {
static logNameToId(name: string): LogId {
const key = name[0]!.toUpperCase() + name.substring(1);
return LogId[key as keyof typeof LogId];
}
public static joinLogId(ids: LogId[]): string {
static joinLogId(ids: LogId[]): string {
return ids.map((id) => Logcat.logIdToName(id)).join(",");
}
public static parseSize(value: number, multiplier: string): number {
static parseSize(value: number, multiplier: string): number {
const MULTIPLIERS = ["", "Ki", "Mi", "Gi"];
return value * 1024 ** (MULTIPLIERS.indexOf(multiplier) || 0);
}
// TODO: logcat: Support output format before Android 10
// ref https://android-review.googlesource.com/c/platform/system/core/+/748128
public static readonly LOG_SIZE_REGEX_10 =
static readonly LOG_SIZE_REGEX_10 =
/(.*): ring buffer is (.*) (.*)B \((.*) (.*)B consumed\), max entry is (.*) B, max payload is (.*) B/;
// Android 11 added `readable` part
// ref https://android-review.googlesource.com/c/platform/system/core/+/1390940
public static readonly LOG_SIZE_REGEX_11 =
static readonly LOG_SIZE_REGEX_11 =
/(.*): ring buffer is (.*) (.*)B \((.*) (.*)B consumed, (.*) (.*)B readable\), max entry is (.*) B, max payload is (.*) B/;
public async getLogSize(ids?: LogId[]): Promise<LogSize[]> {
async getLogSize(ids?: LogId[]): Promise<LogSize[]> {
const { stdout } = await this.adb.subprocess.spawn([
"logcat",
"-g",
@ -469,7 +469,7 @@ export class Logcat extends AdbCommandBase {
return result;
}
public async clear(ids?: LogId[]) {
async clear(ids?: LogId[]) {
await this.adb.subprocess.spawnAndWait([
"logcat",
"-c",
@ -477,7 +477,7 @@ export class Logcat extends AdbCommandBase {
]);
}
public binary(options?: LogcatOptions): ReadableStream<AndroidLogEntry> {
binary(options?: LogcatOptions): ReadableStream<AndroidLogEntry> {
return new WrapReadableStream(async () => {
// TODO: make `spawn` return synchronously with streams pending
// so it's easier to chain them.

View file

@ -3,12 +3,12 @@ import { describe, expect, it } from "@jest/globals";
import { OverlayDisplay } from "./overlay-display.js";
describe("OverlayDisplay", () => {
describe("OverlayDisplayDevicesFormat", () => {
describe("SETTING_FORMAT", () => {
// values are from https://cs.android.com/android/platform/superproject/+/master:frameworks/base/packages/SettingsLib/res/values/arrays.xml;l=468;drc=60c1d392225bc6e1601693c7d5cfdf1d7f510015
it("should parse 0 device", () => {
expect(
OverlayDisplay.OverlayDisplayDevicesFormat.parse({
OverlayDisplay.SETTING_FORMAT.parse({
value: "",
position: 0,
}),
@ -17,7 +17,7 @@ describe("OverlayDisplay", () => {
it("should parse 1 mode", () => {
expect(
OverlayDisplay.OverlayDisplayDevicesFormat.parse({
OverlayDisplay.SETTING_FORMAT.parse({
value: "720x480/142",
position: 0,
}),
@ -37,7 +37,7 @@ describe("OverlayDisplay", () => {
it("should parse 2 modes", () => {
expect(
OverlayDisplay.OverlayDisplayDevicesFormat.parse({
OverlayDisplay.SETTING_FORMAT.parse({
value: "1920x1080/320|3840x2160/640",
position: 0,
}),
@ -62,7 +62,7 @@ describe("OverlayDisplay", () => {
it("should parse 2 device", () => {
expect(
OverlayDisplay.OverlayDisplayDevicesFormat.parse({
OverlayDisplay.SETTING_FORMAT.parse({
value: "1280x720/213;1920x1080/320",
position: 0,
}),
@ -92,7 +92,7 @@ describe("OverlayDisplay", () => {
it("should parse flags", () => {
expect(
OverlayDisplay.OverlayDisplayDevicesFormat.parse({
OverlayDisplay.SETTING_FORMAT.parse({
value: "1920x1080/320|3840x2160/640,secure",
position: 0,
}),

View file

@ -18,12 +18,11 @@ export interface OverlayDisplayDevice {
}
export class OverlayDisplay extends AdbCommandBase {
private settings: Settings;
#settings: Settings;
public static readonly OVERLAY_DISPLAY_DEVICES_KEY =
"overlay_display_devices";
static readonly SETTING_KEY = "overlay_display_devices";
public static readonly OverlayDisplayDevicesFormat = p.separated(
static readonly SETTING_FORMAT = p.separated(
";",
p.sequence(
{
@ -62,14 +61,14 @@ export class OverlayDisplay extends AdbCommandBase {
constructor(adb: Adb) {
super(adb);
this.settings = new Settings(adb);
this.#settings = new Settings(adb);
}
public async get() {
return OverlayDisplay.OverlayDisplayDevicesFormat.parse({
value: await this.settings.get(
async get() {
return OverlayDisplay.SETTING_FORMAT.parse({
value: await this.#settings.get(
"global",
OverlayDisplay.OVERLAY_DISPLAY_DEVICES_KEY,
OverlayDisplay.SETTING_KEY,
),
position: 0,
}).map((device) => ({
@ -82,11 +81,11 @@ export class OverlayDisplay extends AdbCommandBase {
}));
}
public async set(devices: OverlayDisplayDevice[]) {
await this.settings.put(
async set(devices: OverlayDisplayDevice[]) {
await this.#settings.put(
"global",
OverlayDisplay.OVERLAY_DISPLAY_DEVICES_KEY,
OverlayDisplay.OverlayDisplayDevicesFormat.stringify(
OverlayDisplay.SETTING_KEY,
OverlayDisplay.SETTING_FORMAT.stringify(
devices.map((device) => {
const flags: (
| "secure"

View file

@ -219,14 +219,14 @@ export interface PackageManagerListPackagesResult {
}
export class PackageManager extends AdbCommandBase {
private _cmd: Cmd;
#cmd: Cmd;
public constructor(adb: Adb) {
constructor(adb: Adb) {
super(adb);
this._cmd = new Cmd(adb);
this.#cmd = new Cmd(adb);
}
private buildArguments<T>(
#buildArguments<T>(
commands: string[],
options: Partial<T> | undefined,
map: Record<keyof T, string>,
@ -253,27 +253,27 @@ export class PackageManager extends AdbCommandBase {
return args;
}
private buildInstallArguments(
#buildInstallArguments(
options: Partial<PackageManagerInstallOptions> | undefined,
): string[] {
return this.buildArguments(
return this.#buildArguments(
["install"],
options,
PACKAGE_MANAGER_INSTALL_OPTIONS_MAP,
);
}
public async install(
async install(
apks: string[],
options?: Partial<PackageManagerInstallOptions>,
): Promise<string> {
const args = this.buildInstallArguments(options);
const args = this.#buildInstallArguments(options);
// WIP: old version of pm doesn't support multiple apks
args.push(...apks);
return await this.adb.subprocess.spawnAndWaitLegacy(args);
}
public async pushAndInstallStream(
async pushAndInstallStream(
stream: ReadableStream<Consumable<Uint8Array>>,
options?: Partial<PackageManagerInstallOptions>,
): Promise<ReadableStream<string>> {
@ -295,7 +295,7 @@ export class PackageManager extends AdbCommandBase {
// and `cmd package` launches faster than `pm`.
// But `cmd package` can't read `/data/local/tmp` folder due to SELinux policy,
// so installing a file must use `pm`.
const args = this.buildInstallArguments(options);
const args = this.#buildInstallArguments(options);
args.push(filePath);
const process = await AdbSubprocessNoneProtocol.raw(
this.adb,
@ -309,7 +309,7 @@ export class PackageManager extends AdbCommandBase {
});
}
public async installStream(
async installStream(
size: number,
stream: ReadableStream<Consumable<Uint8Array>>,
options?: Partial<PackageManagerInstallOptions>,
@ -317,20 +317,20 @@ export class PackageManager extends AdbCommandBase {
// Android 7 added both `cmd` command and streaming install support,
// we can't detect whether `pm` supports streaming install,
// so we detect `cmd` command support instead.
if (!this._cmd.supportsCmd) {
if (!this.#cmd.supportsCmd) {
return this.pushAndInstallStream(stream, options);
}
const args = this.buildInstallArguments(options);
const args = this.#buildInstallArguments(options);
// Remove `pm` from args, final command will starts with `cmd package install`
args.shift();
args.push("-S", size.toString());
const process = await this._cmd.spawn(false, "package", ...args);
const process = await this.#cmd.spawn(false, "package", ...args);
await stream.pipeTo(process.stdin);
return process.stdout.pipeThrough(new DecodeUtf8Stream());
}
public static parsePackageListItem(
static parsePackageListItem(
line: string,
): PackageManagerListPackagesResult {
line = line.substring("package:".length);
@ -381,10 +381,10 @@ export class PackageManager extends AdbCommandBase {
};
}
public async listPackages(
async listPackages(
options?: Partial<PackageManagerListPackagesOptions>,
): Promise<PackageManagerListPackagesResult[]> {
const args = this.buildArguments(
const args = this.#buildArguments(
["list", "packages"],
options,
PACKAGE_MANAGER_LIST_PACKAGES_OPTIONS_MAP,

View file

@ -24,12 +24,12 @@ export interface SettingsPutOptions extends SettingsOptions {
export class Settings extends AdbCommandBase {
#cmd: Cmd;
public constructor(adb: Adb) {
constructor(adb: Adb) {
super(adb);
this.#cmd = new Cmd(adb);
}
public async base(
async base(
verb: string,
namespace: SettingsNamespace,
options: SettingsOptions | undefined,
@ -61,7 +61,7 @@ export class Settings extends AdbCommandBase {
return output.stdout;
}
public async get(
async get(
namespace: SettingsNamespace,
key: string,
options?: SettingsOptions,
@ -71,7 +71,7 @@ export class Settings extends AdbCommandBase {
return output.substring(0, output.length - 1);
}
public async delete(
async delete(
namespace: SettingsNamespace,
key: string,
options?: SettingsOptions,
@ -79,7 +79,7 @@ export class Settings extends AdbCommandBase {
await this.base("delete", namespace, options, key);
}
public async put(
async put(
namespace: SettingsNamespace,
key: string,
value: string,
@ -95,18 +95,18 @@ export class Settings extends AdbCommandBase {
await this.base("put", namespace, options, ...args);
}
public reset(
reset(
namespace: SettingsNamespace,
mode: SettingsResetMode,
options?: SettingsOptions,
): Promise<void>;
public reset(
reset(
namespace: SettingsNamespace,
packageName: string,
tag?: string,
options?: SettingsOptions,
): Promise<void>;
public async reset(
async reset(
namespace: SettingsNamespace,
modeOrPackageName: string,
tagOrOptions?: string | SettingsOptions,

View file

@ -6,7 +6,7 @@ export class ParseError extends Error {
return this.#expected;
}
public constructor(expected: string[]) {
constructor(expected: string[]) {
super(`Expected ${expected.join(", ")}`);
this.#expected = expected;
}
@ -29,34 +29,20 @@ type UnionResult<T extends readonly Format<unknown>[]> = Exclude<
undefined
>;
type UnionToIntersection<T> = (
T extends unknown ? (x: T) => void : never
) extends (x: infer R) => void
? R
: never;
type SequenceResult<
T extends readonly (
| Format<unknown>
| { name: string; format: Format<unknown> }
)[],
> = UnionToIntersection<
{
[K in keyof T]: T[K] extends {
name: string;
format: Format<unknown>;
}
? Record<
T[K]["name"],
T[K]["format"] extends Format<infer F>
? Exclude<F, undefined>
> = {
[K in keyof T as K extends `${number}`
? T[K] extends { name: infer N extends string }
? N
: never
>
: never]: T[K] extends { format: Format<infer F> } ? F : never;
} extends infer R extends Record<string, unknown>
? R
: never;
}[number]
>;
type Evaluate<T> = T extends infer U ? { [K in keyof U]: U[K] } : never;
export const p = {
literal: <T extends string>(value: T): Format<T> => ({
@ -174,7 +160,7 @@ export const p = {
)[],
>(
...args: T
): Format<Evaluate<SequenceResult<T>>> => ({
): Format<SequenceResult<T>> => ({
parse(reader: Reader) {
const result: Record<string, unknown> = {};
for (const part of args) {
@ -184,9 +170,9 @@ export const p = {
void part.parse(reader);
}
}
return result as Evaluate<SequenceResult<T>>;
return result as SequenceResult<T>;
},
stringify: (value: Evaluate<SequenceResult<T>>) => {
stringify: (value: SequenceResult<T>) => {
let result = "";
for (const part of args) {
if ("name" in part) {

View file

@ -77,7 +77,7 @@ export class AoaHidDevice {
* @param reportDescriptor The HID report descriptor.
* @returns An instance of AoaHidDevice to send events.
*/
public static async register(
static async register(
device: USBDevice,
accessoryId: number,
reportDescriptor: Uint8Array
@ -87,19 +87,19 @@ export class AoaHidDevice {
return new AoaHidDevice(device, accessoryId);
}
private _device: USBDevice;
private _accessoryId: number;
#device: USBDevice;
#accessoryId: number;
private constructor(device: USBDevice, accessoryId: number) {
this._device = device;
this._accessoryId = accessoryId;
constructor(device: USBDevice, accessoryId: number) {
this.#device = device;
this.#accessoryId = accessoryId;
}
public async sendInputReport(event: Uint8Array) {
await aoaHidSendInputReport(this._device, this._accessoryId, event);
async sendInputReport(event: Uint8Array) {
await aoaHidSendInputReport(this.#device, this.#accessoryId, event);
}
public async unregister() {
await aoaHidUnregister(this._device, this._accessoryId);
async unregister() {
await aoaHidUnregister(this.#device, this.#accessoryId);
}
}

View file

@ -229,7 +229,7 @@ export class HidKeyboard {
* It's compatible with the legacy boot protocol. (1 byte modifier, 1 byte reserved, 6 bytes key codes).
* Technically it doesn't need to be compatible with the legacy boot protocol, but it's the most common implementation.
*/
public static readonly DESCRIPTOR = new Uint8Array(
static readonly DESCRIPTOR = new Uint8Array(
// prettier-ignore
[
0x05, 0x01, // Usage Page (Generic Desktop)
@ -271,35 +271,35 @@ export class HidKeyboard {
]
);
private _modifiers = 0;
private _keys: Set<HidKeyCode> = new Set();
#modifiers = 0;
#keys: Set<HidKeyCode> = new Set();
public down(key: HidKeyCode) {
down(key: HidKeyCode) {
if (key >= HidKeyCode.ControlLeft && key <= HidKeyCode.MetaRight) {
this._modifiers |= 1 << (key - HidKeyCode.ControlLeft);
this.#modifiers |= 1 << (key - HidKeyCode.ControlLeft);
} else {
this._keys.add(key);
this.#keys.add(key);
}
}
public up(key: HidKeyCode) {
up(key: HidKeyCode) {
if (key >= HidKeyCode.ControlLeft && key <= HidKeyCode.MetaRight) {
this._modifiers &= ~(1 << (key - HidKeyCode.ControlLeft));
this.#modifiers &= ~(1 << (key - HidKeyCode.ControlLeft));
} else {
this._keys.delete(key);
this.#keys.delete(key);
}
}
public reset() {
this._modifiers = 0;
this._keys.clear();
reset() {
this.#modifiers = 0;
this.#keys.clear();
}
public serializeInputReport() {
serializeInputReport() {
const buffer = new Uint8Array(8);
buffer[0] = this._modifiers;
buffer[0] = this.#modifiers;
let i = 2;
for (const key of this._keys) {
for (const key of this.#keys) {
buffer[i] = key;
i += 1;
if (i >= 8) {

View file

@ -1,5 +1,5 @@
export class HidMouse {
public static readonly descriptor = new Uint8Array(
static readonly descriptor = new Uint8Array(
// prettier-ignore
[
0x05, 0x01, // Usage Page (Generic Desktop)
@ -42,7 +42,7 @@ export class HidMouse {
]
);
public static serializeInputReport(
static serializeInputReport(
movementX: number,
movementY: number,
buttons: number,

View file

@ -81,40 +81,40 @@ interface Finger {
* A ten-point touch screen.
*/
export class HidTouchScreen {
public static readonly FINGER_DESCRIPTOR = FINGER_DESCRIPTOR;
static readonly FINGER_DESCRIPTOR = FINGER_DESCRIPTOR;
public static readonly DESCRIPTOR = DESCRIPTOR;
static readonly DESCRIPTOR = DESCRIPTOR;
private fingers: Map<number, Finger> = new Map();
#fingers: Map<number, Finger> = new Map();
public down(id: number, x: number, y: number) {
if (this.fingers.size >= 10) {
down(id: number, x: number, y: number) {
if (this.#fingers.size >= 10) {
return;
}
this.fingers.set(id, {
this.#fingers.set(id, {
x,
y,
});
}
public move(id: number, x: number, y: number) {
const finger = this.fingers.get(id);
move(id: number, x: number, y: number) {
const finger = this.#fingers.get(id);
if (finger) {
finger.x = x;
finger.y = y;
}
}
public up(id: number) {
this.fingers.delete(id);
up(id: number) {
this.#fingers.delete(id);
}
public serializeInputReport(): Uint8Array {
serializeInputReport(): Uint8Array {
const report = new Uint8Array(1 + 6 * 10);
report[0] = this.fingers.size;
report[0] = this.#fingers.size;
let offset = 1;
for (const [id, finger] of this.fingers) {
for (const [id, finger] of this.#fingers) {
report[offset] = id;
offset += 1;

View file

@ -43,11 +43,11 @@ describe("BTree", () => {
function validateTree(tree: BTree) {
if (tree.size === 0) {
expect(tree["_root"].keyCount).toBe(0);
expect(tree.root.keyCount).toBe(0);
return;
}
validateNode(tree["_root"], true);
validateNode(tree.root, true);
}
for (let order = 3; order < 10; order += 1) {

View file

@ -32,7 +32,7 @@ export class BTreeNode {
height: number;
children: BTreeNode[];
public constructor(
constructor(
order: number,
keys: Int32Array,
keyCount: number,
@ -124,7 +124,7 @@ export class BTreeNode {
};
}
public search(value: number): number {
search(value: number): number {
let start = 0;
let end = this.keyCount - 1;
while (start <= end) {
@ -140,7 +140,7 @@ export class BTreeNode {
return ~start;
}
public has(value: number): boolean {
has(value: number): boolean {
let index = this.search(value);
if (index >= 0) {
@ -155,7 +155,7 @@ export class BTreeNode {
return false;
}
public add(value: number): BTreeInsertionResult | boolean {
add(value: number): BTreeInsertionResult | boolean {
let index = this.search(value);
if (index >= 0) {
return false;
@ -188,7 +188,7 @@ export class BTreeNode {
return true;
}
public delete(value: number): boolean {
delete(value: number): boolean {
let index = this.search(value);
if (index >= 0) {
this.deleteAt(index);
@ -209,7 +209,7 @@ export class BTreeNode {
return deleted;
}
public max(): number {
max(): number {
if (this.height === 0) {
return this.keys[this.keyCount - 1]!;
}
@ -315,7 +315,7 @@ export class BTreeNode {
this.balance(index);
}
public *[Symbol.iterator](): Generator<number, void, void> {
*[Symbol.iterator](): Generator<number, void, void> {
if (this.height > 0) {
for (let i = 0; i < this.keyCount; i += 1) {
yield* this.children[i]!;
@ -331,27 +331,31 @@ export class BTreeNode {
}
export class BTree {
private _order: number;
public get order() {
return this._order;
#order: number;
get order() {
return this.#order;
}
private _root: BTreeNode;
private _size = 0;
public get size() {
return this._size;
#root: BTreeNode;
/** @internal */
get root() {
return this.#root;
}
public constructor(order: number) {
this._order = order;
#size = 0;
get size() {
return this.#size;
}
constructor(order: number) {
this.#order = order;
const keys = new Int32Array(order - 1);
const children = new Array<BTreeNode>(order);
this._root = new BTreeNode(order, keys, 0, 0, children);
this.#root = new BTreeNode(order, keys, 0, 0, children);
}
public has(value: number) {
let node = this._root;
has(value: number) {
let node = this.#root;
while (true) {
const index = node.search(value);
if (index >= 0) {
@ -365,50 +369,50 @@ export class BTree {
}
}
public add(value: number) {
const split = this._root.add(value);
add(value: number) {
const split = this.#root.add(value);
if (typeof split === "object") {
const keys = new Int32Array(this._order - 1);
const keys = new Int32Array(this.#order - 1);
keys[0] = split.key;
const children = new Array<BTreeNode>(this._order);
children[0] = this._root;
const children = new Array<BTreeNode>(this.#order);
children[0] = this.#root;
children[1] = split.child;
this._root = new BTreeNode(
this._order,
this.#root = new BTreeNode(
this.#order,
keys,
1,
this._root.height + 1,
this.#root.height + 1,
children,
);
}
if (split) {
this._size += 1;
this.#size += 1;
}
return !!split;
}
public delete(value: number) {
const deleted = this._root.delete(value);
delete(value: number) {
const deleted = this.#root.delete(value);
if (deleted) {
if (this._root.height > 0 && this._root.keyCount === 0) {
this._root = this._root.children[0]!;
if (this.#root.height > 0 && this.#root.keyCount === 0) {
this.#root = this.#root.children[0]!;
}
this._size -= 1;
this.#size -= 1;
}
return deleted;
}
public clear() {
this._root.keyCount = 0;
this._root.height = 0;
clear() {
this.#root.keyCount = 0;
this.#root.height = 0;
// immediately release all references
this._root.children = new Array<BTreeNode>(this._order);
this._size = 0;
this.#root.children = new Array<BTreeNode>(this.#order);
this.#size = 0;
}
public [Symbol.iterator]() {
return this._root[Symbol.iterator]();
[Symbol.iterator]() {
return this.#root[Symbol.iterator]();
}
}

View file

@ -3,28 +3,28 @@ export interface Disposable {
}
export class AutoDisposable implements Disposable {
private disposables: Disposable[] = [];
#disposables: Disposable[] = [];
public constructor() {
constructor() {
this.dispose = this.dispose.bind(this);
}
protected addDisposable<T extends Disposable>(disposable: T): T {
this.disposables.push(disposable);
this.#disposables.push(disposable);
return disposable;
}
public dispose() {
for (const disposable of this.disposables) {
dispose() {
for (const disposable of this.#disposables) {
disposable.dispose();
}
this.disposables = [];
this.#disposables = [];
}
}
export class DisposableList extends AutoDisposable {
public add<T extends Disposable>(disposable: T): T {
add<T extends Disposable>(disposable: T): T {
return this.addDisposable(disposable);
}
}

View file

@ -23,7 +23,7 @@ export interface AddEventListener<TEvent, TResult = unknown> {
export class EventEmitter<TEvent, TResult = unknown> implements Disposable {
protected readonly listeners: EventListenerInfo<TEvent, TResult>[] = [];
public constructor() {
constructor() {
this.event = this.event.bind(this);
}
@ -42,10 +42,7 @@ export class EventEmitter<TEvent, TResult = unknown> implements Disposable {
return remove;
}
public event: AddEventListener<TEvent, TResult> = <
TThis,
TArgs extends unknown[],
>(
event: AddEventListener<TEvent, TResult> = <TThis, TArgs extends unknown[]>(
listener: EventListener<TEvent, TThis, TArgs, TResult>,
thisArg?: TThis,
...args: TArgs
@ -63,13 +60,13 @@ export class EventEmitter<TEvent, TResult = unknown> implements Disposable {
return this.addEventListener(info);
};
public fire(e: TEvent) {
fire(e: TEvent) {
for (const info of this.listeners.slice()) {
info.listener.apply(info.thisArg, [e, ...info.args]);
}
}
public dispose() {
dispose() {
this.listeners.length = 0;
}
}

View file

@ -1,12 +1,12 @@
export abstract class PcmPlayer<T> {
protected abstract sourceName: string;
private _context: AudioContext;
private _worklet: AudioWorkletNode | undefined;
private _buffer: T[] = [];
#context: AudioContext;
#worklet: AudioWorkletNode | undefined;
#buffers: T[] = [];
constructor(sampleRate: number) {
this._context = new AudioContext({
this.#context = new AudioContext({
latencyHint: "interactive",
sampleRate,
});
@ -14,38 +14,38 @@ export abstract class PcmPlayer<T> {
protected abstract feedCore(worklet: AudioWorkletNode, source: T): void;
public feed(source: T) {
if (this._worklet === undefined) {
this._buffer.push(source);
feed(source: T) {
if (this.#worklet === undefined) {
this.#buffers.push(source);
return;
}
this.feedCore(this._worklet, source);
this.feedCore(this.#worklet, source);
}
public async start() {
await this._context.audioWorklet.addModule(
async start() {
await this.#context.audioWorklet.addModule(
new URL("./worker.js", import.meta.url),
);
this._worklet = new AudioWorkletNode(this._context, this.sourceName, {
this.#worklet = new AudioWorkletNode(this.#context, this.sourceName, {
numberOfInputs: 0,
numberOfOutputs: 1,
outputChannelCount: [2],
});
this._worklet.connect(this._context.destination);
this.#worklet.connect(this.#context.destination);
for (const source of this._buffer) {
this.feedCore(this._worklet, source);
for (const source of this.#buffers) {
this.feedCore(this.#worklet, source);
}
this._buffer.length = 0;
this.#buffers.length = 0;
}
async stop() {
this._worklet?.disconnect();
this._worklet = undefined;
this.#worklet?.disconnect();
this.#worklet = undefined;
await this._context.close();
await this.#context.close();
}
}

View file

@ -2,16 +2,16 @@ abstract class SourceProcessor<T>
extends AudioWorkletProcessor
implements AudioWorkletProcessorImpl
{
private _sources: T[] = [];
private _sourceSampleCount = 0;
#sources: T[] = [];
#sourceSampleCount = 0;
public constructor() {
constructor() {
super();
this.port.onmessage = (event) => {
const data = event.data as ArrayBuffer[];
const [source, length] = this.createSource(data);
this._sources.push(source);
this._sourceSampleCount += length;
this.#sources.push(source);
this.#sourceSampleCount += length;
};
}
@ -26,13 +26,13 @@ abstract class SourceProcessor<T>
// Resample source catch up with output
// TODO: should we limit the minimum and maximum speed?
// TODO: this simple resample method changes pitch
const sourceIndexStep = this._sourceSampleCount > 48000 ? 1.02 : 1;
const sourceIndexStep = this.#sourceSampleCount > 48000 ? 1.02 : 1;
let sourceIndex = 0;
while (this._sources.length > 0 && outputIndex < outputLength) {
while (this.#sources.length > 0 && outputIndex < outputLength) {
const beginSourceIndex = sourceIndex | 0;
let source: T | undefined = this._sources[0];
let source: T | undefined = this.#sources[0];
[source, sourceIndex, outputIndex] = this.copyChunk(
sourceIndex,
sourceIndexStep,
@ -44,16 +44,16 @@ abstract class SourceProcessor<T>
);
const consumedSampleCount = (sourceIndex | 0) - beginSourceIndex;
this._sourceSampleCount -= consumedSampleCount;
this.#sourceSampleCount -= consumedSampleCount;
sourceIndex -= consumedSampleCount;
if (source) {
// Output full
this._sources[0] = source;
this.#sources[0] = source;
return true;
}
this._sources.shift();
this.#sources.shift();
}
if (outputIndex < outputLength) {
@ -98,7 +98,7 @@ class Int16SourceProcessor
): [
source: Int16Array | undefined,
sourceIndex: number,
outputIndex: number
outputIndex: number,
] {
const sourceLength = source.length;
let sourceSampleIndex = sourceIndex << 1;
@ -145,7 +145,7 @@ class Float32SourceProcessor extends SourceProcessor<Float32Array> {
): [
source: Float32Array | undefined,
sourceIndex: number,
outputIndex: number
outputIndex: number,
] {
const sourceLength = source.length;
let sourceSampleIndex = sourceIndex << 1;
@ -192,7 +192,7 @@ class Float32PlanerSourceProcessor extends SourceProcessor<Float32Array[]> {
): [
source: Float32Array[] | undefined,
sourceIndex: number,
outputIndex: number
outputIndex: number,
] {
const sourceLeft = source[0]!;
const sourceRight = source[1]!;

View file

@ -38,56 +38,54 @@ function initialize() {
}
export class TinyH264Decoder implements ScrcpyVideoDecoder {
public static readonly capabilities: Record<
string,
ScrcpyVideoDecoderCapability
> = {
static readonly capabilities: Record<string, ScrcpyVideoDecoderCapability> =
{
h264: {
maxProfile: AndroidAvcProfile.Baseline,
maxLevel: AndroidAvcLevel.Level4,
},
};
private _renderer: HTMLCanvasElement;
public get renderer() {
return this._renderer;
#renderer: HTMLCanvasElement;
get renderer() {
return this.#renderer;
}
private _frameRendered = 0;
public get frameRendered() {
return this._frameRendered;
#frameRendered = 0;
get frameRendered() {
return this.#frameRendered;
}
private _frameSkipped = 0;
public get frameSkipped() {
return this._frameSkipped;
#frameSkipped = 0;
get frameSkipped() {
return this.#frameSkipped;
}
private _writable: WritableStream<ScrcpyMediaStreamPacket>;
public get writable() {
return this._writable;
#writable: WritableStream<ScrcpyMediaStreamPacket>;
get writable() {
return this.#writable;
}
private _yuvCanvas: YuvCanvas | undefined;
private _initializer: PromiseResolver<TinyH264Wrapper> | undefined;
#yuvCanvas: YuvCanvas | undefined;
#initializer: PromiseResolver<TinyH264Wrapper> | undefined;
public constructor() {
constructor() {
void initialize();
this._renderer = document.createElement("canvas");
this.#renderer = document.createElement("canvas");
this._writable = new WritableStream<ScrcpyMediaStreamPacket>({
this.#writable = new WritableStream<ScrcpyMediaStreamPacket>({
write: async (packet) => {
switch (packet.type) {
case "configuration":
await this.configure(packet.data);
await this.#configure(packet.data);
break;
case "data": {
if (!this._initializer) {
if (!this.#initializer) {
throw new Error("Decoder not configured");
}
const wrapper = await this._initializer.promise;
const wrapper = await this.#initializer.promise;
wrapper.feed(packet.data.slice().buffer);
break;
}
@ -96,14 +94,14 @@ export class TinyH264Decoder implements ScrcpyVideoDecoder {
});
}
private async configure(data: Uint8Array) {
async #configure(data: Uint8Array) {
this.dispose();
this._initializer = new PromiseResolver<TinyH264Wrapper>();
this.#initializer = new PromiseResolver<TinyH264Wrapper>();
const { YuvBuffer, YuvCanvas } = await initialize();
if (!this._yuvCanvas) {
this._yuvCanvas = YuvCanvas.attach(this._renderer);
if (!this.#yuvCanvas) {
this.#yuvCanvas = YuvCanvas.attach(this.#renderer);
}
const {
@ -134,12 +132,12 @@ export class TinyH264Decoder implements ScrcpyVideoDecoder {
});
const wrapper = await createTinyH264Wrapper();
this._initializer.resolve(wrapper);
this.#initializer.resolve(wrapper);
const uPlaneOffset = encodedWidth * encodedHeight;
const vPlaneOffset = uPlaneOffset + chromaWidth * chromaHeight;
wrapper.onPictureReady(({ data }) => {
this._frameRendered += 1;
this.#frameRendered += 1;
const array = new Uint8Array(data);
const frame = YuvBuffer.frame(
format,
@ -147,17 +145,17 @@ export class TinyH264Decoder implements ScrcpyVideoDecoder {
YuvBuffer.chromaPlane(format, array, chromaWidth, uPlaneOffset),
YuvBuffer.chromaPlane(format, array, chromaWidth, vPlaneOffset),
);
this._yuvCanvas!.drawFrame(frame);
this.#yuvCanvas!.drawFrame(frame);
});
wrapper.feed(data.slice().buffer);
}
public dispose(): void {
this._initializer?.promise
dispose(): void {
this.#initializer?.promise
.then((wrapper) => wrapper.dispose())
// NOOP: It's disposed so nobody cares about the error
.catch(NOOP);
this._initializer = undefined;
this.#initializer = undefined;
}
}

View file

@ -146,8 +146,8 @@ declare module "yuv-canvas" {
import type { YUVFrame } from "yuv-buffer";
export default class YUVCanvas {
public static attach(canvas: HTMLCanvasElement): YUVCanvas;
static attach(canvas: HTMLCanvasElement): YUVCanvas;
public drawFrame(data: YUVFrame): void;
drawFrame(data: YUVFrame): void;
}
}

View file

@ -1,6 +1,6 @@
import { PromiseResolver } from "@yume-chan/async";
import { AutoDisposable, EventEmitter } from "@yume-chan/event";
import type { Disposable } from "@yume-chan/event";
import { AutoDisposable, EventEmitter } from "@yume-chan/event";
let worker: Worker | undefined;
let workerReady = false;
@ -36,28 +36,27 @@ function subscribePictureReady(
}
export class TinyH264Wrapper extends AutoDisposable {
public readonly streamId: number;
readonly streamId: number;
private readonly pictureReadyEvent =
new EventEmitter<PictureReadyEventArgs>();
public get onPictureReady() {
return this.pictureReadyEvent.event;
readonly #pictureReadyEvent = new EventEmitter<PictureReadyEventArgs>();
get onPictureReady() {
return this.#pictureReadyEvent.event;
}
public constructor(streamId: number) {
constructor(streamId: number) {
super();
this.streamId = streamId;
this.addDisposable(
subscribePictureReady(streamId, this.handlePictureReady),
subscribePictureReady(streamId, this.#handlePictureReady),
);
}
private handlePictureReady = (e: PictureReadyEventArgs) => {
this.pictureReadyEvent.fire(e);
#handlePictureReady = (e: PictureReadyEventArgs) => {
this.#pictureReadyEvent.fire(e);
};
public feed(data: ArrayBuffer) {
feed(data: ArrayBuffer) {
worker!.postMessage(
{
type: "decode",
@ -70,7 +69,7 @@ export class TinyH264Wrapper extends AutoDisposable {
);
}
public override dispose() {
override dispose() {
super.dispose();
worker!.postMessage({
type: "release",

View file

@ -27,63 +27,61 @@ function toUint32Le(data: Uint8Array, offset: number) {
}
export class WebCodecsDecoder implements ScrcpyVideoDecoder {
public static isSupported() {
static isSupported() {
return typeof globalThis.VideoDecoder !== "undefined";
}
public static readonly capabilities: Record<
string,
ScrcpyVideoDecoderCapability
> = {
static readonly capabilities: Record<string, ScrcpyVideoDecoderCapability> =
{
h264: {},
h265: {},
};
private _codec: ScrcpyVideoCodecId;
public get codec() {
return this._codec;
#codec: ScrcpyVideoCodecId;
get codec() {
return this.#codec;
}
private _writable: WritableStream<ScrcpyMediaStreamPacket>;
public get writable() {
return this._writable;
#writable: WritableStream<ScrcpyMediaStreamPacket>;
get writable() {
return this.#writable;
}
private _renderer: HTMLCanvasElement;
public get renderer() {
return this._renderer;
#renderer: HTMLCanvasElement;
get renderer() {
return this.#renderer;
}
private _frameRendered = 0;
public get frameRendered() {
return this._frameRendered;
#frameRendered = 0;
get frameRendered() {
return this.#frameRendered;
}
private _frameSkipped = 0;
public get frameSkipped() {
return this._frameSkipped;
#frameSkipped = 0;
get frameSkipped() {
return this.#frameSkipped;
}
private context: CanvasRenderingContext2D;
private decoder: VideoDecoder;
private _config: Uint8Array | undefined;
#context: CanvasRenderingContext2D;
#decoder: VideoDecoder;
#config: Uint8Array | undefined;
private currentFrameRendered = false;
private animationFrameId = 0;
#currentFrameRendered = false;
#animationFrameId = 0;
public constructor(codec: ScrcpyVideoCodecId) {
this._codec = codec;
constructor(codec: ScrcpyVideoCodecId) {
this.#codec = codec;
this._renderer = document.createElement("canvas");
this.#renderer = document.createElement("canvas");
this.context = this._renderer.getContext("2d")!;
this.decoder = new VideoDecoder({
this.#context = this.#renderer.getContext("2d")!;
this.#decoder = new VideoDecoder({
output: (frame) => {
if (this.currentFrameRendered) {
this._frameSkipped += 1;
if (this.#currentFrameRendered) {
this.#frameSkipped += 1;
} else {
this.currentFrameRendered = true;
this._frameRendered += 1;
this.#currentFrameRendered = true;
this.#frameRendered += 1;
}
// PERF: H.264 renderer may draw multiple frames in one vertical sync interval to minimize latency.
@ -92,7 +90,7 @@ export class WebCodecsDecoder implements ScrcpyVideoDecoder {
// But this ensures users can always see the most up-to-date screen.
// This is also the behavior of official Scrcpy client.
// https://github.com/Genymobile/scrcpy/issues/3679
this.context.drawImage(frame, 0, 0);
this.#context.drawImage(frame, 0, 0);
frame.close();
},
error(e) {
@ -100,29 +98,29 @@ export class WebCodecsDecoder implements ScrcpyVideoDecoder {
},
});
this._writable = new WritableStream<ScrcpyMediaStreamPacket>({
this.#writable = new WritableStream<ScrcpyMediaStreamPacket>({
write: (packet) => {
switch (packet.type) {
case "configuration":
this.configure(packet.data);
this.#configure(packet.data);
break;
case "data":
this.decode(packet);
this.#decode(packet);
break;
}
},
});
this.onFramePresented();
this.#onFramePresented();
}
private onFramePresented = () => {
this.currentFrameRendered = false;
this.animationFrameId = requestAnimationFrame(this.onFramePresented);
#onFramePresented = () => {
this.#currentFrameRendered = false;
this.#animationFrameId = requestAnimationFrame(this.#onFramePresented);
};
private configure(data: Uint8Array) {
switch (this._codec) {
#configure(data: Uint8Array) {
switch (this.#codec) {
case ScrcpyVideoCodecId.H264: {
const {
profileIndex,
@ -132,15 +130,15 @@ export class WebCodecsDecoder implements ScrcpyVideoDecoder {
croppedHeight,
} = h264ParseConfiguration(data);
this._renderer.width = croppedWidth;
this._renderer.height = croppedHeight;
this.#renderer.width = croppedWidth;
this.#renderer.height = croppedHeight;
// https://www.rfc-editor.org/rfc/rfc6381#section-3.3
// ISO Base Media File Format Name Space
const codec = `avc1.${[profileIndex, constraintSet, levelIndex]
.map(toHex)
.join("")}`;
this.decoder.configure({
this.#decoder.configure({
codec: codec,
optimizeForLatency: true,
});
@ -158,8 +156,8 @@ export class WebCodecsDecoder implements ScrcpyVideoDecoder {
croppedHeight,
} = h265ParseConfiguration(data);
this._renderer.width = croppedWidth;
this._renderer.height = croppedHeight;
this.#renderer.width = croppedWidth;
this.#renderer.height = croppedHeight;
const codec = [
"hev1",
@ -175,36 +173,36 @@ export class WebCodecsDecoder implements ScrcpyVideoDecoder {
.toString(16)
.toUpperCase(),
].join(".");
this.decoder.configure({
this.#decoder.configure({
codec,
optimizeForLatency: true,
});
break;
}
}
this._config = data;
this.#config = data;
}
private decode(packet: ScrcpyMediaStreamDataPacket) {
if (this.decoder.state !== "configured") {
#decode(packet: ScrcpyMediaStreamDataPacket) {
if (this.#decoder.state !== "configured") {
return;
}
// WebCodecs requires configuration data to be with the first frame.
// https://www.w3.org/TR/webcodecs-avc-codec-registration/#encodedvideochunk-type
let data: Uint8Array;
if (this._config !== undefined) {
if (this.#config !== undefined) {
data = new Uint8Array(
this._config.byteLength + packet.data.byteLength,
this.#config.byteLength + packet.data.byteLength,
);
data.set(this._config, 0);
data.set(packet.data, this._config.byteLength);
this._config = undefined;
data.set(this.#config, 0);
data.set(packet.data, this.#config.byteLength);
this.#config = undefined;
} else {
data = packet.data;
}
this.decoder.decode(
this.#decoder.decode(
new EncodedVideoChunk({
// Treat `undefined` as `key`, otherwise won't decode.
type: packet.keyframe === false ? "delta" : "key",
@ -214,10 +212,10 @@ export class WebCodecsDecoder implements ScrcpyVideoDecoder {
);
}
public dispose() {
cancelAnimationFrame(this.animationFrameId);
if (this.decoder.state !== "closed") {
this.decoder.close();
dispose() {
cancelAnimationFrame(this.#animationFrameId);
if (this.#decoder.state !== "closed") {
this.#decoder.close();
}
}
}

View file

@ -231,96 +231,93 @@ export function naluRemoveEmulation(buffer: Uint8Array) {
}
export class NaluSodbBitReader {
private readonly _nalu: Uint8Array;
private readonly _byteLength: number;
private readonly _stopBitIndex: number;
readonly #nalu: Uint8Array;
readonly #byteLength: number;
readonly #stopBitIndex: number;
private _zeroCount = 0;
private _bytePosition = -1;
private _bitPosition = -1;
private _byte = 0;
#zeroCount = 0;
#bytePosition = -1;
#bitPosition = -1;
#byte = 0;
public get byteLength() {
return this._byteLength;
get byteLength() {
return this.#byteLength;
}
public get stopBitIndex() {
return this._stopBitIndex;
get stopBitIndex() {
return this.#stopBitIndex;
}
public get bytePosition() {
return this._bytePosition;
get bytePosition() {
return this.#bytePosition;
}
public get bitPosition() {
return this._bitPosition;
get bitPosition() {
return this.#bitPosition;
}
public get ended() {
get ended() {
return (
this._bytePosition === this._byteLength &&
this._bitPosition === this._stopBitIndex
this.#bytePosition === this.#byteLength &&
this.#bitPosition === this.#stopBitIndex
);
}
public constructor(nalu: Uint8Array) {
this._nalu = nalu;
constructor(nalu: Uint8Array) {
this.#nalu = nalu;
for (let i = nalu.length - 1; i >= 0; i -= 1) {
if (this._nalu[i] === 0) {
if (this.#nalu[i] === 0) {
continue;
}
const byte = nalu[i]!;
for (let j = 0; j < 8; j += 1) {
if (((byte >> j) & 1) === 1) {
this._byteLength = i;
this._stopBitIndex = j;
this.readByte();
this.#byteLength = i;
this.#stopBitIndex = j;
this.#readByte();
return;
}
}
}
throw new Error("End bit not found");
throw new Error("Stop bit not found");
}
private readByte() {
this._byte = this._nalu[this._bytePosition]!;
if (this._zeroCount === 2 && this._byte === 3) {
this._zeroCount = 0;
this._bytePosition += 1;
this.readByte();
#readByte() {
this.#byte = this.#nalu[this.#bytePosition]!;
if (this.#zeroCount === 2 && this.#byte === 3) {
this.#zeroCount = 0;
this.#bytePosition += 1;
this.#readByte();
return;
}
if (this._byte === 0) {
this._zeroCount += 1;
if (this.#byte === 0) {
this.#zeroCount += 1;
} else {
this._zeroCount = 0;
this.#zeroCount = 0;
}
}
public next() {
if (this._bitPosition === -1) {
this._bitPosition = 7;
this._bytePosition += 1;
this.readByte();
next() {
if (this.#bitPosition === -1) {
this.#bitPosition = 7;
this.#bytePosition += 1;
this.#readByte();
}
if (
this._bytePosition === this._byteLength &&
this._bitPosition === this._stopBitIndex
) {
if (this.ended) {
throw new Error("Bit index out of bounds");
}
const value = (this._byte >> this._bitPosition) & 1;
this._bitPosition -= 1;
const value = (this.#byte >> this.#bitPosition) & 1;
this.#bitPosition -= 1;
return value;
}
public read(length: number): number {
read(length: number): number {
if (length > 32) {
throw new Error("Read length too large");
}
@ -332,13 +329,13 @@ export class NaluSodbBitReader {
return result;
}
public skip(length: number) {
skip(length: number) {
for (let i = 0; i < length; i += 1) {
this.next();
}
}
public decodeExponentialGolombNumber(): number {
decodeExponentialGolombNumber(): number {
let length = 0;
while (this.next() === 0) {
length += 1;
@ -349,14 +346,35 @@ export class NaluSodbBitReader {
return ((1 << length) | this.read(length)) - 1;
}
public peek(length: number) {
const { _zeroCount, _bytePosition, _bitPosition, _byte } = this;
#save() {
return {
zeroCount: this.#zeroCount,
bytePosition: this.#bytePosition,
bitPosition: this.#bitPosition,
byte: this.#byte,
};
}
#restore(state: {
zeroCount: number;
bytePosition: number;
bitPosition: number;
byte: number;
}) {
this.#zeroCount = state.zeroCount;
this.#bytePosition = state.bytePosition;
this.#bitPosition = state.bitPosition;
this.#byte = state.byte;
}
peek(length: number) {
const state = this.#save();
const result = this.read(length);
Object.assign(this, { _zeroCount, _bytePosition, _bitPosition, _byte });
this.#restore(state);
return result;
}
public readBytes(length: number): Uint8Array {
readBytes(length: number): Uint8Array {
const result = new Uint8Array(length);
for (let i = 0; i < length; i += 1) {
result[i] = this.read(8);
@ -364,10 +382,10 @@ export class NaluSodbBitReader {
return result;
}
public peekBytes(length: number): Uint8Array {
const { _zeroCount, _bytePosition, _bitPosition, _byte } = this;
peekBytes(length: number): Uint8Array {
const state = this.#save();
const result = this.readBytes(length);
Object.assign(this, { _zeroCount, _bytePosition, _bitPosition, _byte });
this.#restore(state);
return result;
}
}

View file

@ -15,12 +15,11 @@ import { ScrcpyControlMessageType } from "./type.js";
* so Scrcpy server can remove the previously hovering pointer.
*/
export class ScrcpyHoverHelper {
// AFAIK, only mouse and pen can have hover state
// and you can't have two mouses or pens.
// So remember the last hovering pointer is enough.
private lastHoverMessage: ScrcpyInjectTouchControlMessage | undefined;
// There can be only one hovering pointer (either mouse or pen,
// touch can have multiple pointers but no hovering state).
#lastHoverMessage: ScrcpyInjectTouchControlMessage | undefined;
public process(
process(
message: Omit<ScrcpyInjectTouchControlMessage, "type">,
): ScrcpyInjectTouchControlMessage[] {
const result: ScrcpyInjectTouchControlMessage[] = [];
@ -28,21 +27,21 @@ export class ScrcpyHoverHelper {
// A different pointer appeared,
// Cancel previously hovering pointer so Scrcpy server can free up the pointer ID.
if (
this.lastHoverMessage &&
this.lastHoverMessage.pointerId !== message.pointerId
this.#lastHoverMessage &&
this.#lastHoverMessage.pointerId !== message.pointerId
) {
// TODO: Inject MotionEvent.ACTION_HOVER_EXIT
// From testing, it seems no App cares about this event.
result.push({
...this.lastHoverMessage,
...this.#lastHoverMessage,
action: AndroidMotionEventAction.Up,
});
this.lastHoverMessage = undefined;
this.#lastHoverMessage = undefined;
}
if (message.action === AndroidMotionEventAction.HoverMove) {
// TODO: Inject MotionEvent.ACTION_HOVER_ENTER
this.lastHoverMessage = message as ScrcpyInjectTouchControlMessage;
this.#lastHoverMessage = message as ScrcpyInjectTouchControlMessage;
}
(message as ScrcpyInjectTouchControlMessage).type =

View file

@ -17,40 +17,38 @@ import {
} from "./type.js";
export class ScrcpyControlMessageSerializer {
private _options: ScrcpyOptions<object>;
private _typeValues: ScrcpyControlMessageTypeValue;
private _scrollController: ScrcpyScrollController;
#options: ScrcpyOptions<object>;
#typeValues: ScrcpyControlMessageTypeValue;
#scrollController: ScrcpyScrollController;
public constructor(options: ScrcpyOptions<object>) {
this._options = options;
this._typeValues = new ScrcpyControlMessageTypeValue(options);
this._scrollController = options.createScrollController();
constructor(options: ScrcpyOptions<object>) {
this.#options = options;
this.#typeValues = new ScrcpyControlMessageTypeValue(options);
this.#scrollController = options.createScrollController();
}
public injectKeyCode(
message: Omit<ScrcpyInjectKeyCodeControlMessage, "type">,
) {
injectKeyCode(message: Omit<ScrcpyInjectKeyCodeControlMessage, "type">) {
return ScrcpyInjectKeyCodeControlMessage.serialize(
this._typeValues.fillMessageType(
this.#typeValues.fillMessageType(
message,
ScrcpyControlMessageType.InjectKeyCode,
),
);
}
public injectText(text: string) {
injectText(text: string) {
return ScrcpyInjectTextControlMessage.serialize({
text,
type: this._typeValues.get(ScrcpyControlMessageType.InjectText),
type: this.#typeValues.get(ScrcpyControlMessageType.InjectText),
});
}
/**
* `pressure` is a float value between 0 and 1.
*/
public injectTouch(message: Omit<ScrcpyInjectTouchControlMessage, "type">) {
return this._options.serializeInjectTouchControlMessage(
this._typeValues.fillMessageType(
injectTouch(message: Omit<ScrcpyInjectTouchControlMessage, "type">) {
return this.#options.serializeInjectTouchControlMessage(
this.#typeValues.fillMessageType(
message,
ScrcpyControlMessageType.InjectTouch,
),
@ -60,36 +58,34 @@ export class ScrcpyControlMessageSerializer {
/**
* `scrollX` and `scrollY` are float values between 0 and 1.
*/
public injectScroll(
message: Omit<ScrcpyInjectScrollControlMessage, "type">,
) {
return this._scrollController.serializeScrollMessage(
this._typeValues.fillMessageType(
injectScroll(message: Omit<ScrcpyInjectScrollControlMessage, "type">) {
return this.#scrollController.serializeScrollMessage(
this.#typeValues.fillMessageType(
message,
ScrcpyControlMessageType.InjectScroll,
),
);
}
public backOrScreenOn(action: AndroidKeyEventAction) {
return this._options.serializeBackOrScreenOnControlMessage({
backOrScreenOn(action: AndroidKeyEventAction) {
return this.#options.serializeBackOrScreenOnControlMessage({
action,
type: this._typeValues.get(ScrcpyControlMessageType.BackOrScreenOn),
type: this.#typeValues.get(ScrcpyControlMessageType.BackOrScreenOn),
});
}
public setScreenPowerMode(mode: AndroidScreenPowerMode) {
setScreenPowerMode(mode: AndroidScreenPowerMode) {
return ScrcpySetScreenPowerModeControlMessage.serialize({
mode,
type: this._typeValues.get(
type: this.#typeValues.get(
ScrcpyControlMessageType.SetScreenPowerMode,
),
});
}
public rotateDevice() {
rotateDevice() {
return ScrcpyRotateDeviceControlMessage.serialize({
type: this._typeValues.get(ScrcpyControlMessageType.RotateDevice),
type: this.#typeValues.get(ScrcpyControlMessageType.RotateDevice),
});
}
}

View file

@ -25,21 +25,21 @@ export enum ScrcpyControlMessageType {
* This class provides a way to get the actual value for a given type.
*/
export class ScrcpyControlMessageTypeValue {
private types: readonly ScrcpyControlMessageType[];
#types: readonly ScrcpyControlMessageType[];
public constructor(options: ScrcpyOptions<object>) {
this.types = options.controlMessageTypes;
constructor(options: ScrcpyOptions<object>) {
this.#types = options.controlMessageTypes;
}
public get(type: ScrcpyControlMessageType): number {
const value = this.types.indexOf(type);
get(type: ScrcpyControlMessageType): number {
const value = this.#types.indexOf(type);
if (value === -1) {
throw new Error("Not supported");
}
return value;
}
public fillMessageType<T extends { type: ScrcpyControlMessageType }>(
fillMessageType<T extends { type: ScrcpyControlMessageType }>(
message: Omit<T, "type">,
type: T["type"],
): T {

View file

@ -16,72 +16,70 @@ import { ScrcpyControlMessageSerializer } from "./serializer.js";
import type { AndroidScreenPowerMode } from "./set-screen-power-mode.js";
export class ScrcpyControlMessageWriter {
private _writer: WritableStreamDefaultWriter<Consumable<Uint8Array>>;
private _serializer: ScrcpyControlMessageSerializer;
#writer: WritableStreamDefaultWriter<Consumable<Uint8Array>>;
#serializer: ScrcpyControlMessageSerializer;
public constructor(
constructor(
writer: WritableStreamDefaultWriter<Consumable<Uint8Array>>,
options: ScrcpyOptions<object>,
) {
this._writer = writer;
this._serializer = new ScrcpyControlMessageSerializer(options);
this.#writer = writer;
this.#serializer = new ScrcpyControlMessageSerializer(options);
}
private async write(message: Uint8Array) {
await ConsumableWritableStream.write(this._writer, message);
async #write(message: Uint8Array) {
await ConsumableWritableStream.write(this.#writer, message);
}
public async injectKeyCode(
async injectKeyCode(
message: Omit<ScrcpyInjectKeyCodeControlMessage, "type">,
) {
await this.write(this._serializer.injectKeyCode(message));
await this.#write(this.#serializer.injectKeyCode(message));
}
public async injectText(text: string) {
await this.write(this._serializer.injectText(text));
async injectText(text: string) {
await this.#write(this.#serializer.injectText(text));
}
/**
* `pressure` is a float value between 0 and 1.
*/
public async injectTouch(
message: Omit<ScrcpyInjectTouchControlMessage, "type">,
) {
await this.write(this._serializer.injectTouch(message));
async injectTouch(message: Omit<ScrcpyInjectTouchControlMessage, "type">) {
await this.#write(this.#serializer.injectTouch(message));
}
/**
* `scrollX` and `scrollY` are float values between 0 and 1.
*/
public async injectScroll(
async injectScroll(
message: Omit<ScrcpyInjectScrollControlMessage, "type">,
) {
const data = this._serializer.injectScroll(message);
const data = this.#serializer.injectScroll(message);
if (data) {
await this.write(data);
await this.#write(data);
}
}
public async backOrScreenOn(action: AndroidKeyEventAction) {
const data = this._serializer.backOrScreenOn(action);
async backOrScreenOn(action: AndroidKeyEventAction) {
const data = this.#serializer.backOrScreenOn(action);
if (data) {
await this.write(data);
await this.#write(data);
}
}
public async setScreenPowerMode(mode: AndroidScreenPowerMode) {
await this.write(this._serializer.setScreenPowerMode(mode));
async setScreenPowerMode(mode: AndroidScreenPowerMode) {
await this.#write(this.#serializer.setScreenPowerMode(mode));
}
public async rotateDevice() {
await this.write(this._serializer.rotateDevice());
async rotateDevice() {
await this.#write(this.#serializer.rotateDevice());
}
public releaseLock() {
this._writer.releaseLock();
releaseLock() {
this.#writer.releaseLock();
}
public async close() {
await this._writer.close();
async close() {
await this.#writer.close();
}
}

View file

@ -27,13 +27,13 @@ const CODEC_OPTION_TYPES: Partial<
};
export class CodecOptions implements ScrcpyOptionValue {
public value: Partial<CodecOptionsInit>;
value: Partial<CodecOptionsInit>;
public constructor(value: Partial<CodecOptionsInit> = {}) {
constructor(value: Partial<CodecOptionsInit> = {}) {
this.value = value;
}
public toOptionValue(): string | undefined {
toOptionValue(): string | undefined {
const entries = Object.entries(this.value).filter(
([, value]) => value !== undefined,
);

View file

@ -38,7 +38,7 @@ import type { ScrcpyScrollController } from "./scroll.js";
import { ScrcpyScrollController1_16 } from "./scroll.js";
export class ScrcpyOptions1_16 implements ScrcpyOptions<ScrcpyOptionsInit1_16> {
public static readonly DEFAULTS = {
static readonly DEFAULTS = {
logLevel: ScrcpyLogLevel1_16.Debug,
maxSize: 0,
bitRate: 8_000_000,
@ -54,7 +54,7 @@ export class ScrcpyOptions1_16 implements ScrcpyOptions<ScrcpyOptionsInit1_16> {
codecOptions: new CodecOptions(),
} as const satisfies Required<ScrcpyOptionsInit1_16>;
public static readonly SERIALIZE_ORDER = [
static readonly SERIALIZE_ORDER = [
"logLevel",
"maxSize",
"bitRate",
@ -70,11 +70,11 @@ export class ScrcpyOptions1_16 implements ScrcpyOptions<ScrcpyOptionsInit1_16> {
"codecOptions",
] as const satisfies readonly (keyof ScrcpyOptionsInit1_16)[];
public static serialize<T>(options: T, order: readonly (keyof T)[]) {
static serialize<T>(options: T, order: readonly (keyof T)[]) {
return order.map((key) => toScrcpyOptionValue(options[key], "-"));
}
public static async parseCString(
static async parseCString(
stream: AsyncExactReadable,
maxLength: number,
): Promise<string> {
@ -83,55 +83,51 @@ export class ScrcpyOptions1_16 implements ScrcpyOptions<ScrcpyOptionsInit1_16> {
return result;
}
public static async parseUint16BE(
stream: AsyncExactReadable,
): Promise<number> {
static async parseUint16BE(stream: AsyncExactReadable): Promise<number> {
const buffer = await stream.readExactly(NumberFieldType.Uint16.size);
return NumberFieldType.Uint16.deserialize(buffer, false);
}
public static async parseUint32BE(
stream: AsyncExactReadable,
): Promise<number> {
static async parseUint32BE(stream: AsyncExactReadable): Promise<number> {
const buffer = await stream.readExactly(NumberFieldType.Uint32.size);
return NumberFieldType.Uint32.deserialize(buffer, false);
}
public value: Required<ScrcpyOptionsInit1_16>;
value: Required<ScrcpyOptionsInit1_16>;
public readonly defaults: Required<ScrcpyOptionsInit1_16> =
readonly defaults: Required<ScrcpyOptionsInit1_16> =
ScrcpyOptions1_16.DEFAULTS;
public readonly controlMessageTypes: readonly ScrcpyControlMessageType[] =
readonly controlMessageTypes: readonly ScrcpyControlMessageType[] =
SCRCPY_CONTROL_MESSAGE_TYPES_1_16;
public constructor(init: ScrcpyOptionsInit1_16) {
constructor(init: ScrcpyOptionsInit1_16) {
this.value = { ...ScrcpyOptions1_16.DEFAULTS, ...init };
}
public serialize(): string[] {
serialize(): string[] {
return ScrcpyOptions1_16.serialize(
this.value,
ScrcpyOptions1_16.SERIALIZE_ORDER,
);
}
public setListEncoders(): void {
setListEncoders(): void {
throw new Error("Not supported");
}
public setListDisplays(): void {
setListDisplays(): void {
// Set to an invalid value
// Server will print valid values before crashing
// (server will crash before opening sockets)
this.value.displayId = -1;
}
public parseEncoder(): ScrcpyEncoder | undefined {
parseEncoder(): ScrcpyEncoder | undefined {
throw new Error("Not supported");
}
public parseDisplay(line: string): ScrcpyDisplay | undefined {
parseDisplay(line: string): ScrcpyDisplay | undefined {
const displayIdRegex = /\s+scrcpy --display (\d+)/;
const match = line.match(displayIdRegex);
if (match) {
@ -142,7 +138,7 @@ export class ScrcpyOptions1_16 implements ScrcpyOptions<ScrcpyOptionsInit1_16> {
return undefined;
}
public parseVideoStreamMetadata(
parseVideoStreamMetadata(
stream: ReadableStream<Uint8Array>,
): ValueOrPromise<ScrcpyVideoStream> {
return (async () => {
@ -160,11 +156,11 @@ export class ScrcpyOptions1_16 implements ScrcpyOptions<ScrcpyOptionsInit1_16> {
})();
}
public parseAudioStreamMetadata(): never {
parseAudioStreamMetadata(): never {
throw new Error("Not supported");
}
public createMediaStreamTransformer(): TransformStream<
createMediaStreamTransformer(): TransformStream<
Uint8Array,
ScrcpyMediaStreamPacket
> {
@ -207,13 +203,13 @@ export class ScrcpyOptions1_16 implements ScrcpyOptions<ScrcpyOptionsInit1_16> {
};
}
public serializeInjectTouchControlMessage(
serializeInjectTouchControlMessage(
message: ScrcpyInjectTouchControlMessage,
): Uint8Array {
return ScrcpyInjectTouchControlMessage1_16.serialize(message);
}
public serializeBackOrScreenOnControlMessage(
serializeBackOrScreenOnControlMessage(
message: ScrcpyBackOrScreenOnControlMessage,
) {
if (message.action === AndroidKeyEventAction.Down) {
@ -223,13 +219,13 @@ export class ScrcpyOptions1_16 implements ScrcpyOptions<ScrcpyOptionsInit1_16> {
return undefined;
}
public serializeSetClipboardControlMessage(
serializeSetClipboardControlMessage(
message: ScrcpySetClipboardControlMessage,
): Uint8Array {
return ScrcpySetClipboardControlMessage1_15.serialize(message);
}
public createScrollController(): ScrcpyScrollController {
createScrollController(): ScrcpyScrollController {
return new ScrcpyScrollController1_16();
}
}

View file

@ -24,31 +24,31 @@ export const ScrcpyInjectScrollControlMessage1_16 = new Struct()
* reaches 1 or -1.
*/
export class ScrcpyScrollController1_16 implements ScrcpyScrollController {
private accumulatedX = 0;
private accumulatedY = 0;
#accumulatedX = 0;
#accumulatedY = 0;
protected processMessage(
message: ScrcpyInjectScrollControlMessage,
): ScrcpyInjectScrollControlMessage | undefined {
this.accumulatedX += message.scrollX;
this.accumulatedY += message.scrollY;
this.#accumulatedX += message.scrollX;
this.#accumulatedY += message.scrollY;
let scrollX = 0;
let scrollY = 0;
if (this.accumulatedX >= 1) {
if (this.#accumulatedX >= 1) {
scrollX = 1;
this.accumulatedX = 0;
} else if (this.accumulatedX <= -1) {
this.#accumulatedX = 0;
} else if (this.#accumulatedX <= -1) {
scrollX = -1;
this.accumulatedX = 0;
this.#accumulatedX = 0;
}
if (this.accumulatedY >= 1) {
if (this.#accumulatedY >= 1) {
scrollY = 1;
this.accumulatedY = 0;
} else if (this.accumulatedY <= -1) {
this.#accumulatedY = 0;
} else if (this.#accumulatedY <= -1) {
scrollY = -1;
this.accumulatedY = 0;
this.#accumulatedY = 0;
}
if (scrollX === 0 && scrollY === 0) {
@ -60,7 +60,7 @@ export class ScrcpyScrollController1_16 implements ScrcpyScrollController {
return message;
}
public serializeScrollMessage(
serializeScrollMessage(
message: ScrcpyInjectScrollControlMessage,
): Uint8Array | undefined {
const processed = this.processMessage(message);

View file

@ -11,17 +11,17 @@ export class ScrcpyOptions1_17 extends ScrcpyOptionsBase<
ScrcpyOptionsInit1_17,
ScrcpyOptions1_16
> {
public static readonly DEFAULTS = {
static readonly DEFAULTS = {
...ScrcpyOptions1_16.DEFAULTS,
encoderName: undefined,
} as const satisfies Required<ScrcpyOptionsInit1_17>;
public static readonly SERIALIZE_ORDER = [
static readonly SERIALIZE_ORDER = [
...ScrcpyOptions1_16.SERIALIZE_ORDER,
"encoderName",
] as const satisfies readonly (keyof ScrcpyOptionsInit1_17)[];
public static parseEncoder(
static parseEncoder(
line: string,
encoderNameRegex: RegExp,
): ScrcpyEncoder | undefined {
@ -32,32 +32,32 @@ export class ScrcpyOptions1_17 extends ScrcpyOptionsBase<
return undefined;
}
public override get defaults(): Required<ScrcpyOptionsInit1_17> {
override get defaults(): Required<ScrcpyOptionsInit1_17> {
return ScrcpyOptions1_17.DEFAULTS;
}
public constructor(init: ScrcpyOptionsInit1_17) {
constructor(init: ScrcpyOptionsInit1_17) {
super(new ScrcpyOptions1_16(init), {
...ScrcpyOptions1_17.DEFAULTS,
...init,
});
}
public override serialize(): string[] {
override serialize(): string[] {
return ScrcpyOptions1_16.serialize(
this.value,
ScrcpyOptions1_17.SERIALIZE_ORDER,
);
}
public override setListEncoders() {
override setListEncoders() {
// Set to an invalid value
// Server will print valid values before crashing
// (server will crash after opening video and control sockets)
this.value.encoderName = "_";
}
public override parseEncoder(line: string): ScrcpyEncoder | undefined {
override parseEncoder(line: string): ScrcpyEncoder | undefined {
return ScrcpyOptions1_17.parseEncoder(
line,
/\s+scrcpy --encoder-name '(.*?)'/,

View file

@ -82,23 +82,23 @@ export class ScrcpyOptions1_18 extends ScrcpyOptionsBase<
ScrcpyOptionsInit1_18,
ScrcpyOptions1_17
> {
public static readonly DEFAULTS = {
static readonly DEFAULTS = {
...ScrcpyOptions1_17.DEFAULTS,
logLevel: ScrcpyLogLevel1_18.Debug,
lockVideoOrientation: ScrcpyVideoOrientation1_18.Unlocked,
powerOffOnClose: false,
} as const satisfies Required<ScrcpyOptionsInit1_18>;
public static readonly SERIALIZE_ORDER = [
static readonly SERIALIZE_ORDER = [
...ScrcpyOptions1_17.SERIALIZE_ORDER,
"powerOffOnClose",
] as const satisfies readonly (keyof ScrcpyOptionsInit1_18)[];
public override get defaults(): Required<ScrcpyOptionsInit1_18> {
override get defaults(): Required<ScrcpyOptionsInit1_18> {
return ScrcpyOptions1_18.DEFAULTS;
}
public override get controlMessageTypes() {
override get controlMessageTypes() {
return SCRCPY_CONTROL_MESSAGE_TYPES_1_18;
}
@ -115,21 +115,21 @@ export class ScrcpyOptions1_18 extends ScrcpyOptionsBase<
);
}
public override serialize(): string[] {
override serialize(): string[] {
return ScrcpyOptions1_16.serialize(
this.value,
ScrcpyOptions1_18.SERIALIZE_ORDER,
);
}
public override parseEncoder(line: string): ScrcpyEncoder | undefined {
override parseEncoder(line: string): ScrcpyEncoder | undefined {
return ScrcpyOptions1_17.parseEncoder(
line,
/\s+scrcpy --encoder '(.*?)'/,
);
}
public override serializeBackOrScreenOnControlMessage(
override serializeBackOrScreenOnControlMessage(
message: ScrcpyBackOrScreenOnControlMessage,
) {
return ScrcpyBackOrScreenOnControlMessage1_18.serialize(message);

View file

@ -30,12 +30,12 @@ export class ScrcpyOptions1_21 extends ScrcpyOptionsBase<
ScrcpyOptionsInit1_21,
ScrcpyOptions1_18
> {
public static readonly DEFAULTS = {
static readonly DEFAULTS = {
...ScrcpyOptions1_18.DEFAULTS,
clipboardAutosync: true,
} as const satisfies Required<ScrcpyOptionsInit1_21>;
public static serialize<T extends object>(
static serialize<T extends object>(
options: T,
defaults: Required<T>,
): string[] {
@ -60,22 +60,22 @@ export class ScrcpyOptions1_21 extends ScrcpyOptionsBase<
return result;
}
public override get defaults(): Required<ScrcpyOptionsInit1_21> {
override get defaults(): Required<ScrcpyOptionsInit1_21> {
return ScrcpyOptions1_21.DEFAULTS;
}
public constructor(init: ScrcpyOptionsInit1_21) {
constructor(init: ScrcpyOptionsInit1_21) {
super(new ScrcpyOptions1_18(init), {
...ScrcpyOptions1_21.DEFAULTS,
...init,
});
}
public override serialize(): string[] {
override serialize(): string[] {
return ScrcpyOptions1_21.serialize(this.value, this.defaults);
}
public override serializeSetClipboardControlMessage(
override serializeSetClipboardControlMessage(
message: ScrcpySetClipboardControlMessage,
): Uint8Array {
return ScrcpySetClipboardControlMessage1_21.serialize(message);

View file

@ -14,25 +14,25 @@ export class ScrcpyOptions1_22 extends ScrcpyOptionsBase<
ScrcpyOptionsInit1_22,
ScrcpyOptions1_21
> {
public static readonly DEFAULTS = {
static readonly DEFAULTS = {
...ScrcpyOptions1_21.DEFAULTS,
downsizeOnError: true,
sendDeviceMeta: true,
sendDummyByte: true,
} as const satisfies Required<ScrcpyOptionsInit1_22>;
public override get defaults(): Required<ScrcpyOptionsInit1_22> {
override get defaults(): Required<ScrcpyOptionsInit1_22> {
return ScrcpyOptions1_22.DEFAULTS;
}
public constructor(init: ScrcpyOptionsInit1_22) {
constructor(init: ScrcpyOptionsInit1_22) {
super(new ScrcpyOptions1_21(init), {
...ScrcpyOptions1_22.DEFAULTS,
...init,
});
}
public override parseVideoStreamMetadata(
override parseVideoStreamMetadata(
stream: ReadableStream<Uint8Array>,
): ValueOrPromise<ScrcpyVideoStream> {
if (!this.value.sendDeviceMeta) {
@ -42,11 +42,11 @@ export class ScrcpyOptions1_22 extends ScrcpyOptionsBase<
}
}
public override serialize(): string[] {
override serialize(): string[] {
return ScrcpyOptions1_21.serialize(this.value, this.defaults);
}
public override createScrollController(): ScrcpyScrollController {
override createScrollController(): ScrcpyScrollController {
return new ScrcpyScrollController1_22();
}
}

View file

@ -13,7 +13,7 @@ export type ScrcpyInjectScrollControlMessage1_22 =
(typeof ScrcpyInjectScrollControlMessage1_22)["TInit"];
export class ScrcpyScrollController1_22 extends ScrcpyScrollController1_16 {
public override serializeScrollMessage(
override serializeScrollMessage(
message: ScrcpyInjectScrollControlMessage1_22,
): Uint8Array | undefined {
const processed = this.processMessage(message);

View file

@ -16,27 +16,27 @@ export class ScrcpyOptions1_23 extends ScrcpyOptionsBase<
ScrcpyOptionsInit1_23,
ScrcpyOptions1_22
> {
public static readonly DEFAULTS = {
static readonly DEFAULTS = {
...ScrcpyOptions1_22.DEFAULTS,
cleanup: true,
} as const satisfies Required<ScrcpyOptionsInit1_23>;
public override get defaults(): Required<ScrcpyOptionsInit1_23> {
override get defaults(): Required<ScrcpyOptionsInit1_23> {
return ScrcpyOptions1_23.DEFAULTS;
}
public constructor(init: ScrcpyOptionsInit1_22) {
constructor(init: ScrcpyOptionsInit1_22) {
super(new ScrcpyOptions1_22(init), {
...ScrcpyOptions1_23.DEFAULTS,
...init,
});
}
public override serialize(): string[] {
override serialize(): string[] {
return ScrcpyOptions1_21.serialize(this.value, this.defaults);
}
public override createMediaStreamTransformer(): TransformStream<
override createMediaStreamTransformer(): TransformStream<
Uint8Array,
ScrcpyMediaStreamPacket
> {

View file

@ -11,23 +11,23 @@ export class ScrcpyOptions1_24 extends ScrcpyOptionsBase<
ScrcpyOptionsInit1_24,
ScrcpyOptions1_23
> {
public static readonly DEFAULTS = {
static readonly DEFAULTS = {
...ScrcpyOptions1_23.DEFAULTS,
powerOn: true,
} as const satisfies Required<ScrcpyOptionsInit1_24>;
public override get defaults(): Required<ScrcpyOptionsInit1_24> {
override get defaults(): Required<ScrcpyOptionsInit1_24> {
return ScrcpyOptions1_24.DEFAULTS;
}
public constructor(init: ScrcpyOptionsInit1_24) {
constructor(init: ScrcpyOptionsInit1_24) {
super(new ScrcpyOptions1_23(init), {
...ScrcpyOptions1_24.DEFAULTS,
...init,
});
}
public override serialize(): string[] {
override serialize(): string[] {
return ScrcpyOptions1_21.serialize(this.value, this.defaults);
}
}

View file

@ -10,22 +10,22 @@ export class ScrcpyOptions1_25 extends ScrcpyOptionsBase<
ScrcpyOptionsInit1_24,
ScrcpyOptions1_24
> {
public override get defaults(): Required<ScrcpyOptionsInit1_24> {
override get defaults(): Required<ScrcpyOptionsInit1_24> {
return ScrcpyOptions1_24.DEFAULTS;
}
public constructor(init: ScrcpyOptionsInit1_24) {
constructor(init: ScrcpyOptionsInit1_24) {
super(new ScrcpyOptions1_24(init), {
...ScrcpyOptions1_24.DEFAULTS,
...init,
});
}
public override serialize(): string[] {
override serialize(): string[] {
return ScrcpyOptions1_21.serialize(this.value, this.defaults);
}
public override createScrollController(): ScrcpyScrollController {
override createScrollController(): ScrcpyScrollController {
return new ScrcpyScrollController1_25();
}
}

View file

@ -45,20 +45,20 @@ export type ScrcpyInjectTouchControlMessage2_0 =
(typeof ScrcpyInjectTouchControlMessage2_0)["TInit"];
export class ScrcpyInstanceId implements ScrcpyOptionValue {
public static readonly NONE = new ScrcpyInstanceId(-1);
static readonly NONE = new ScrcpyInstanceId(-1);
public static random(): ScrcpyInstanceId {
static random(): ScrcpyInstanceId {
// A random 31-bit unsigned integer
return new ScrcpyInstanceId((Math.random() * 0x80000000) | 0);
}
public value: number;
value: number;
public constructor(value: number) {
constructor(value: number) {
this.value = value;
}
public toOptionValue(): string | undefined {
toOptionValue(): string | undefined {
if (this.value < 0) {
return undefined;
}
@ -106,7 +106,7 @@ export class ScrcpyOptions2_0 extends ScrcpyOptionsBase<
ScrcpyOptionsInit2_0,
ScrcpyOptions1_25
> {
public static readonly DEFAULTS = {
static readonly DEFAULTS = {
...omit(ScrcpyOptions1_24.DEFAULTS, [
"bitRate",
"codecOptions",
@ -130,30 +130,30 @@ export class ScrcpyOptions2_0 extends ScrcpyOptionsBase<
sendCodecMeta: true,
} as const satisfies Required<ScrcpyOptionsInit2_0>;
public override get defaults(): Required<ScrcpyOptionsInit2_0> {
override get defaults(): Required<ScrcpyOptionsInit2_0> {
return ScrcpyOptions2_0.DEFAULTS;
}
public constructor(init: ScrcpyOptionsInit2_0) {
constructor(init: ScrcpyOptionsInit2_0) {
super(new ScrcpyOptions1_25(init), {
...ScrcpyOptions2_0.DEFAULTS,
...init,
});
}
public override serialize(): string[] {
override serialize(): string[] {
return ScrcpyOptions1_21.serialize(this.value, this.defaults);
}
public override setListEncoders(): void {
override setListEncoders(): void {
this.value.listEncoders = true;
}
public override setListDisplays(): void {
override setListDisplays(): void {
this.value.listDisplays = true;
}
public override parseEncoder(line: string): ScrcpyEncoder | undefined {
override parseEncoder(line: string): ScrcpyEncoder | undefined {
let match = line.match(
/\s+--video-codec=(.*)\s+--video-encoder='(.*)'/,
);
@ -177,7 +177,7 @@ export class ScrcpyOptions2_0 extends ScrcpyOptionsBase<
return undefined;
}
public override parseDisplay(line: string): ScrcpyDisplay | undefined {
override parseDisplay(line: string): ScrcpyDisplay | undefined {
const match = line.match(/\s+--display=(\d+)\s+\((.*?)\)/);
if (match) {
const display: ScrcpyDisplay = {
@ -191,7 +191,7 @@ export class ScrcpyOptions2_0 extends ScrcpyOptionsBase<
return undefined;
}
public override parseVideoStreamMetadata(
override parseVideoStreamMetadata(
stream: ReadableStream<Uint8Array>,
): ValueOrPromise<ScrcpyVideoStream> {
const { sendDeviceMeta, sendCodecMeta } = this.value;
@ -249,7 +249,7 @@ export class ScrcpyOptions2_0 extends ScrcpyOptionsBase<
})();
}
public override parseAudioStreamMetadata(
override parseAudioStreamMetadata(
stream: ReadableStream<Uint8Array>,
): ValueOrPromise<ScrcpyAudioStreamMetadata> {
return (async (): Promise<ScrcpyAudioStreamMetadata> => {
@ -334,7 +334,7 @@ export class ScrcpyOptions2_0 extends ScrcpyOptionsBase<
})();
}
public override serializeInjectTouchControlMessage(
override serializeInjectTouchControlMessage(
message: ScrcpyInjectTouchControlMessage,
): Uint8Array {
return ScrcpyInjectTouchControlMessage2_0.serialize(message);

View file

@ -12,24 +12,24 @@ export class ScrcpyOptions2_1 extends ScrcpyOptionsBase<
ScrcpyOptionsInit2_1,
ScrcpyOptions2_0
> {
public static readonly DEFAULTS = {
static readonly DEFAULTS = {
...ScrcpyOptions2_0.DEFAULTS,
video: true,
audioSource: "output",
} as const satisfies Required<ScrcpyOptionsInit2_1>;
public override get defaults(): Required<ScrcpyOptionsInit2_1> {
override get defaults(): Required<ScrcpyOptionsInit2_1> {
return ScrcpyOptions2_1.DEFAULTS;
}
public constructor(init: ScrcpyOptionsInit2_1) {
constructor(init: ScrcpyOptionsInit2_1) {
super(new ScrcpyOptions2_0(init), {
...ScrcpyOptions2_1.DEFAULTS,
...init,
});
}
public override serialize(): string[] {
override serialize(): string[] {
return ScrcpyOptions1_21.serialize(this.value, this.defaults);
}
}

View file

@ -21,31 +21,31 @@ export interface ScrcpyVideoStream {
}
export class ScrcpyAudioCodec implements ScrcpyOptionValue {
public static readonly OPUS = new ScrcpyAudioCodec(
static readonly OPUS = new ScrcpyAudioCodec(
"opus",
0x6f_70_75_73,
"audio/opus",
"opus",
);
public static readonly AAC = new ScrcpyAudioCodec(
static readonly AAC = new ScrcpyAudioCodec(
"aac",
0x00_61_61_63,
"audio/aac",
"mp4a.66",
);
public static readonly RAW = new ScrcpyAudioCodec(
static readonly RAW = new ScrcpyAudioCodec(
"raw",
0x00_72_61_77,
"audio/raw",
"1",
);
public readonly optionValue: string;
public readonly metadataValue: number;
public readonly mimeType: string;
public readonly webCodecId: string;
readonly optionValue: string;
readonly metadataValue: number;
readonly mimeType: string;
readonly webCodecId: string;
public constructor(
constructor(
optionValue: string,
metadataValue: number,
mimeType: string,
@ -57,7 +57,7 @@ export class ScrcpyAudioCodec implements ScrcpyOptionValue {
this.webCodecId = webCodecId;
}
public toOptionValue(): string {
toOptionValue(): string {
return this.optionValue;
}
}

View file

@ -130,34 +130,34 @@ export abstract class ScrcpyOptionsBase<
{
protected _base: B;
public abstract get defaults(): Required<T>;
abstract get defaults(): Required<T>;
public get controlMessageTypes(): readonly ScrcpyControlMessageType[] {
get controlMessageTypes(): readonly ScrcpyControlMessageType[] {
return this._base.controlMessageTypes;
}
public readonly value: Required<T>;
readonly value: Required<T>;
public constructor(base: B, value: Required<T>) {
constructor(base: B, value: Required<T>) {
this._base = base;
this.value = value;
}
public abstract serialize(): string[];
abstract serialize(): string[];
public setListEncoders(): void {
setListEncoders(): void {
this._base.setListEncoders();
}
public setListDisplays(): void {
setListDisplays(): void {
this._base.setListDisplays();
}
public parseEncoder(line: string): ScrcpyEncoder | undefined {
parseEncoder(line: string): ScrcpyEncoder | undefined {
return this._base.parseEncoder(line);
}
public parseDisplay(line: string): ScrcpyDisplay | undefined {
parseDisplay(line: string): ScrcpyDisplay | undefined {
return this._base.parseDisplay(line);
}
@ -169,44 +169,44 @@ export abstract class ScrcpyOptionsBase<
*
* The returned video stream may be different from the input stream, and should be used for further processing.
*/
public parseVideoStreamMetadata(
parseVideoStreamMetadata(
stream: ReadableStream<Uint8Array>,
): ValueOrPromise<ScrcpyVideoStream> {
return this._base.parseVideoStreamMetadata(stream);
}
public parseAudioStreamMetadata(
parseAudioStreamMetadata(
stream: ReadableStream<Uint8Array>,
): ValueOrPromise<ScrcpyAudioStreamMetadata> {
return this._base.parseAudioStreamMetadata(stream);
}
public createMediaStreamTransformer(): TransformStream<
createMediaStreamTransformer(): TransformStream<
Uint8Array,
ScrcpyMediaStreamPacket
> {
return this._base.createMediaStreamTransformer();
}
public serializeInjectTouchControlMessage(
serializeInjectTouchControlMessage(
message: ScrcpyInjectTouchControlMessage,
): Uint8Array {
return this._base.serializeInjectTouchControlMessage(message);
}
public serializeBackOrScreenOnControlMessage(
serializeBackOrScreenOnControlMessage(
message: ScrcpyBackOrScreenOnControlMessage,
): Uint8Array | undefined {
return this._base.serializeBackOrScreenOnControlMessage(message);
}
public serializeSetClipboardControlMessage(
serializeSetClipboardControlMessage(
message: ScrcpySetClipboardControlMessage,
): Uint8Array {
return this._base.serializeSetClipboardControlMessage(message);
}
public createScrollController(): ScrcpyScrollController {
createScrollController(): ScrcpyScrollController {
return this._base.createScrollController();
}
}

View file

@ -12,12 +12,12 @@ export class BufferedTransformStream<T>
implements ReadableWritablePair<T, Uint8Array>
{
#readable: ReadableStream<T>;
public get readable() {
get readable() {
return this.#readable;
}
#writable: WritableStream<Uint8Array>;
public get writable() {
get writable() {
return this.#writable;
}

View file

@ -14,19 +14,19 @@ export class BufferedReadableStream implements AsyncExactReadable {
#bufferedLength = 0;
#position = 0;
public get position() {
get position() {
return this.#position;
}
protected readonly stream: ReadableStream<Uint8Array>;
protected readonly reader: ReadableStreamDefaultReader<Uint8Array>;
public constructor(stream: ReadableStream<Uint8Array>) {
constructor(stream: ReadableStream<Uint8Array>) {
this.stream = stream;
this.reader = stream.getReader();
}
private async readSource() {
async #readSource() {
const { done, value } = await this.reader.read();
if (done) {
throw new ExactReadableEndedError();
@ -35,7 +35,7 @@ export class BufferedReadableStream implements AsyncExactReadable {
return value;
}
private async readAsync(length: number, initial?: Uint8Array) {
async #readAsync(length: number, initial?: Uint8Array) {
let result: Uint8Array;
let index: number;
@ -45,7 +45,7 @@ export class BufferedReadableStream implements AsyncExactReadable {
index = initial.byteLength;
length -= initial.byteLength;
} else {
const array = await this.readSource();
const array = await this.#readSource();
if (array.byteLength === length) {
return array;
}
@ -64,7 +64,7 @@ export class BufferedReadableStream implements AsyncExactReadable {
}
while (length > 0) {
const array = await this.readSource();
const array = await this.#readSource();
if (array.byteLength === length) {
result.set(array, index);
return result;
@ -91,7 +91,7 @@ export class BufferedReadableStream implements AsyncExactReadable {
* @param length
* @returns
*/
public readExactly(length: number): Uint8Array | Promise<Uint8Array> {
readExactly(length: number): Uint8Array | Promise<Uint8Array> {
// PERF: Add a synchronous path for reading from internal buffer
if (this.#buffered) {
const array = this.#buffered;
@ -107,10 +107,10 @@ export class BufferedReadableStream implements AsyncExactReadable {
this.#buffered = undefined;
this.#bufferedLength = 0;
this.#bufferedOffset = 0;
return this.readAsync(length, array.subarray(offset));
return this.#readAsync(length, array.subarray(offset));
}
return this.readAsync(length);
return this.#readAsync(length);
}
/**
@ -118,7 +118,7 @@ export class BufferedReadableStream implements AsyncExactReadable {
* all data from the wrapped stream.
* @returns A `ReadableStream`
*/
public release(): ReadableStream<Uint8Array> {
release(): ReadableStream<Uint8Array> {
if (this.#bufferedLength > 0) {
return new PushReadableStream<Uint8Array>(async (controller) => {
// Put the remaining data back to the stream
@ -147,7 +147,7 @@ export class BufferedReadableStream implements AsyncExactReadable {
}
}
public cancel(reason?: unknown) {
cancel(reason?: unknown) {
return this.reader.cancel(reason);
}
}

View file

@ -41,7 +41,7 @@ export class ConcatStringStream {
this.#readableController.error(reason);
},
});
public get writable(): WritableStream<string> {
get writable(): WritableStream<string> {
return this.#writable;
}
@ -51,11 +51,11 @@ export class ConcatStringStream {
this.#readableController = controller;
},
}) as ConcatStringReadableStream;
public get readable(): ConcatStringReadableStream {
get readable(): ConcatStringReadableStream {
return this.#readable;
}
public constructor() {
constructor() {
void Object.defineProperties(this.#readable, {
then: {
get: () =>
@ -127,7 +127,7 @@ export class ConcatBufferStream {
this.#readableController.error(reason);
},
});
public get writable(): WritableStream<Uint8Array> {
get writable(): WritableStream<Uint8Array> {
return this.#writable;
}
@ -137,11 +137,11 @@ export class ConcatBufferStream {
this.#readableController = controller;
},
}) as ConcatBufferReadableStream;
public get readable(): ConcatBufferReadableStream {
get readable(): ConcatBufferReadableStream {
return this.#readable;
}
public constructor() {
constructor() {
void Object.defineProperties(this.#readable, {
then: {
get: () =>

View file

@ -29,25 +29,25 @@ export class Consumable<T> {
readonly #task: Task;
readonly #resolver: PromiseResolver<void>;
public readonly value: T;
public readonly consumed: Promise<void>;
readonly value: T;
readonly consumed: Promise<void>;
public constructor(value: T) {
constructor(value: T) {
this.#task = createTask("Consumable");
this.value = value;
this.#resolver = new PromiseResolver<void>();
this.consumed = this.#resolver.promise;
}
public consume() {
consume() {
this.#resolver.resolve();
}
public error(error: any) {
error(error: any) {
this.#resolver.reject(error);
}
public async tryConsume<U>(callback: (value: T) => U) {
async tryConsume<U>(callback: (value: T) => U) {
try {
// eslint-disable-next-line @typescript-eslint/await-thenable
const result = await this.#task.run(() => callback(this.value));
@ -70,7 +70,7 @@ async function enqueue<T>(
}
export class WrapConsumableStream<T> extends TransformStream<T, Consumable<T>> {
public constructor() {
constructor() {
super({
async transform(chunk, controller) {
await enqueue(controller, chunk);
@ -83,7 +83,7 @@ export class UnwrapConsumableStream<T> extends TransformStream<
Consumable<T>,
T
> {
public constructor() {
constructor() {
super({
transform(chunk, controller) {
controller.enqueue(chunk.value);
@ -110,7 +110,7 @@ export interface ConsumableReadableStreamSource<T> {
}
export class ConsumableReadableStream<T> extends ReadableStream<Consumable<T>> {
public constructor(
constructor(
source: ConsumableReadableStreamSource<T>,
strategy?: QueuingStrategy<T>,
) {
@ -168,7 +168,7 @@ export interface ConsumableWritableStreamSink<T> {
}
export class ConsumableWritableStream<T> extends WritableStream<Consumable<T>> {
public static async write<T>(
static async write<T>(
writer: WritableStreamDefaultWriter<Consumable<T>>,
value: T,
) {
@ -177,7 +177,7 @@ export class ConsumableWritableStream<T> extends WritableStream<Consumable<T>> {
await consumable.consumed;
}
public constructor(
constructor(
sink: ConsumableWritableStreamSink<T>,
strategy?: QueuingStrategy<T>,
) {
@ -232,7 +232,7 @@ export class ConsumableTransformStream<I, O> extends TransformStream<
Consumable<I>,
Consumable<O>
> {
public constructor(transformer: ConsumableTransformer<I, O>) {
constructor(transformer: ConsumableTransformer<I, O>) {
let wrappedController:
| ConsumableReadableStreamController<O>
| undefined;
@ -271,7 +271,7 @@ export class ConsumableInspectStream<T> extends TransformStream<
Consumable<T>,
Consumable<T>
> {
public constructor(callback: (value: T) => void) {
constructor(callback: (value: T) => void) {
super({
transform(chunk, controller) {
callback(chunk.value);

View file

@ -3,7 +3,7 @@ import { decodeUtf8 } from "@yume-chan/struct";
import { TransformStream } from "./stream.js";
export class DecodeUtf8Stream extends TransformStream<Uint8Array, string> {
public constructor() {
constructor() {
super({
transform(chunk, controller) {
controller.enqueue(decodeUtf8(chunk));

View file

@ -9,7 +9,7 @@ export class BufferCombiner {
#offset: number;
#available: number;
public constructor(size: number) {
constructor(size: number) {
this.#capacity = size;
this.#buffer = new Uint8Array(size);
this.#offset = 0;
@ -23,7 +23,7 @@ export class BufferCombiner {
* A generator that yields buffers of specified size.
* It may yield the same buffer multiple times, consume the data before calling `next`.
*/
public *push(data: Uint8Array): Generator<Uint8Array, void, void> {
*push(data: Uint8Array): Generator<Uint8Array, void, void> {
let offset = 0;
let available = data.byteLength;
@ -65,7 +65,7 @@ export class BufferCombiner {
}
}
public flush(): Uint8Array | undefined {
flush(): Uint8Array | undefined {
if (this.#offset === 0) {
return undefined;
}
@ -81,7 +81,7 @@ export class DistributionStream extends ConsumableTransformStream<
Uint8Array,
Uint8Array
> {
public constructor(size: number, combine = false) {
constructor(size: number, combine = false) {
const combiner = combine ? new BufferCombiner(size) : undefined;
super({
async transform(chunk, controller) {

View file

@ -50,22 +50,22 @@ export class DuplexStreamFactory<R, W> {
#writers: WritableStreamDefaultWriter<W>[] = [];
#writableClosed = false;
public get writableClosed() {
get writableClosed() {
return this.#writableClosed;
}
#closed = new PromiseResolver<void>();
public get closed() {
get closed() {
return this.#closed.promise;
}
readonly #options: DuplexStreamFactoryOptions;
public constructor(options?: DuplexStreamFactoryOptions) {
constructor(options?: DuplexStreamFactoryOptions) {
this.#options = options ?? {};
}
public wrapReadable(readable: ReadableStream<R>): WrapReadableStream<R> {
wrapReadable(readable: ReadableStream<R>): WrapReadableStream<R> {
return new WrapReadableStream<R>({
start: (controller) => {
this.#readableControllers.push(controller);
@ -82,7 +82,7 @@ export class DuplexStreamFactory<R, W> {
});
}
public createWritable(stream: WritableStream<W>): WritableStream<W> {
createWritable(stream: WritableStream<W>): WritableStream<W> {
const writer = stream.getWriter();
this.#writers.push(writer);
@ -104,7 +104,7 @@ export class DuplexStreamFactory<R, W> {
});
}
public async close() {
async close() {
if (this.#writableClosed) {
return;
}
@ -122,7 +122,7 @@ export class DuplexStreamFactory<R, W> {
}
}
public async dispose() {
async dispose() {
this.#writableClosed = true;
this.#closed.resolve();

View file

@ -25,7 +25,7 @@ export class PushReadableStream<T> extends ReadableStream<T> {
* when the `Promise` is resolved, and be errored when the `Promise` is rejected.
* @param strategy
*/
public constructor(
constructor(
source: PushReadableStreamSource<T>,
strategy?: QueuingStrategy<T>,
) {

View file

@ -20,7 +20,7 @@ function* split(
}
export class SplitStringStream extends TransformStream<string, string> {
public constructor(separator: string) {
constructor(separator: string) {
super({
transform(chunk, controller) {
for (const part of split(chunk, separator)) {

View file

@ -6,7 +6,7 @@ import { BufferedTransformStream } from "./buffered-transform.js";
export class StructDeserializeStream<
T extends Struct<any, any, any, any>,
> extends BufferedTransformStream<StructValueType<T>> {
public constructor(struct: T) {
constructor(struct: T) {
super((stream) => {
return struct.deserialize(stream);
});

View file

@ -42,11 +42,11 @@ function getWrappedReadableStream<T>(
* 3. Convert native `ReadableStream`s to polyfilled ones so they can `pipe` between.
*/
export class WrapReadableStream<T> extends ReadableStream<T> {
public readable!: ReadableStream<T>;
readable!: ReadableStream<T>;
#reader!: ReadableStreamDefaultReader<T>;
public constructor(
constructor(
wrapper:
| ReadableStream<T>
| WrapReadableStreamStart<T>

View file

@ -30,11 +30,11 @@ async function getWrappedWritableStream<T>(
}
export class WrapWritableStream<T> extends WritableStream<T> {
public writable!: WritableStream<T>;
writable!: WritableStream<T>;
#writer!: WritableStreamDefaultWriter<T>;
public constructor(
constructor(
wrapper:
| WritableStream<T>
| WrapWritableStreamStart<T>
@ -73,7 +73,7 @@ export class WrapWritableStream<T> extends WritableStream<T> {
});
}
public bePipedThroughFrom<U>(transformer: TransformStream<U, T>) {
bePipedThroughFrom<U>(transformer: TransformStream<U, T>) {
let promise: Promise<void>;
return new WrapWritableStream<U>({
start: () => {

View file

@ -12,13 +12,13 @@ describe("StructFieldDefinition", () => {
describe(".constructor", () => {
it("should save the `options` parameter", () => {
class MockFieldDefinition extends StructFieldDefinition<number> {
public constructor(options: number) {
constructor(options: number) {
super(options);
}
public getSize(): number {
getSize(): number {
throw new Error("Method not implemented.");
}
public create(
create(
options: Readonly<StructOptions>,
struct: StructValue,
value: unknown,
@ -28,17 +28,17 @@ describe("StructFieldDefinition", () => {
void value;
throw new Error("Method not implemented.");
}
public override deserialize(
override deserialize(
options: Readonly<StructOptions>,
stream: ExactReadable,
struct: StructValue,
): StructFieldValue<this>;
public override deserialize(
override deserialize(
options: Readonly<StructOptions>,
stream: AsyncExactReadable,
struct: StructValue,
): Promise<StructFieldValue<this>>;
public deserialize(
deserialize(
options: Readonly<StructOptions>,
stream: ExactReadable | AsyncExactReadable,
struct: StructValue,

View file

@ -19,17 +19,17 @@ export abstract class StructFieldDefinition<
* When `T` is a type initiated `StructFieldDefinition`,
* use `T['TValue']` to retrieve its `TValue` type parameter.
*/
public readonly TValue!: TValue;
readonly TValue!: TValue;
/**
* When `T` is a type initiated `StructFieldDefinition`,
* use `T['TOmitInitKey']` to retrieve its `TOmitInitKey` type parameter.
*/
public readonly TOmitInitKey!: TOmitInitKey;
readonly TOmitInitKey!: TOmitInitKey;
public readonly options: TOptions;
readonly options: TOptions;
public constructor(options: TOptions) {
constructor(options: TOptions) {
this.options = options;
}
@ -38,12 +38,12 @@ export abstract class StructFieldDefinition<
*
* Actual size can be retrieved from `StructFieldValue#getSize`
*/
public abstract getSize(): number;
abstract getSize(): number;
/**
* When implemented in derived classes, creates a `StructFieldValue` from a given `value`.
*/
public abstract create(
abstract create(
options: Readonly<StructOptions>,
structValue: StructValue,
value: TValue,
@ -55,12 +55,12 @@ export abstract class StructFieldDefinition<
*
* `SyncPromise` can be used to simplify implementation.
*/
public abstract deserialize(
abstract deserialize(
options: Readonly<StructOptions>,
stream: ExactReadable,
structValue: StructValue,
): StructFieldValue<this>;
public abstract deserialize(
abstract deserialize(
options: Readonly<StructOptions>,
stream: AsyncExactReadable,
struct: StructValue,

View file

@ -12,7 +12,7 @@ describe("StructFieldValue", () => {
describe(".constructor", () => {
it("should save parameters", () => {
class MockStructFieldValue extends StructFieldValue {
public serialize(dataView: DataView, offset: number): void {
serialize(dataView: DataView, offset: number): void {
void dataView;
void offset;
throw new Error("Method not implemented.");
@ -40,10 +40,10 @@ describe("StructFieldValue", () => {
describe("#getSize", () => {
it("should return same value as definition's", () => {
class MockFieldDefinition extends StructFieldDefinition {
public getSize(): number {
getSize(): number {
return 42;
}
public create(
create(
options: Readonly<StructOptions>,
struct: StructValue,
value: unknown,
@ -54,17 +54,17 @@ describe("StructFieldValue", () => {
throw new Error("Method not implemented.");
}
public override deserialize(
override deserialize(
options: Readonly<StructOptions>,
stream: ExactReadable,
struct: StructValue,
): StructFieldValue<this>;
public override deserialize(
override deserialize(
options: Readonly<StructOptions>,
stream: AsyncExactReadable,
struct: StructValue,
): Promise<StructFieldValue<this>>;
public override deserialize(
override deserialize(
options: Readonly<StructOptions>,
stream: ExactReadable | AsyncExactReadable,
struct: StructValue,
@ -77,7 +77,7 @@ describe("StructFieldValue", () => {
}
class MockStructFieldValue extends StructFieldValue {
public serialize(dataView: DataView, offset: number): void {
serialize(dataView: DataView, offset: number): void {
void dataView;
void offset;
throw new Error("Method not implemented.");
@ -98,7 +98,7 @@ describe("StructFieldValue", () => {
describe("#set", () => {
it("should update its internal value", () => {
class MockStructFieldValue extends StructFieldValue {
public serialize(dataView: DataView, offset: number): void {
serialize(dataView: DataView, offset: number): void {
void dataView;
void offset;
throw new Error("Method not implemented.");

View file

@ -16,15 +16,15 @@ export abstract class StructFieldValue<
> = StructFieldDefinition<any, any, any>,
> {
/** Gets the definition associated with this runtime value */
public readonly definition: TDefinition;
readonly definition: TDefinition;
/** Gets the options of the associated `Struct` */
public readonly options: Readonly<StructOptions>;
readonly options: Readonly<StructOptions>;
/** Gets the associated `Struct` instance */
public readonly struct: StructValue;
readonly struct: StructValue;
public get hasCustomAccessors(): boolean {
get hasCustomAccessors(): boolean {
return (
this.get !== StructFieldValue.prototype.get ||
this.set !== StructFieldValue.prototype.set
@ -33,7 +33,7 @@ export abstract class StructFieldValue<
protected value: TDefinition["TValue"];
public constructor(
constructor(
definition: TDefinition,
options: Readonly<StructOptions>,
struct: StructValue,
@ -50,26 +50,26 @@ export abstract class StructFieldValue<
*
* When overridden in derived classes, can have custom logic to calculate the actual size.
*/
public getSize(): number {
getSize(): number {
return this.definition.getSize();
}
/**
* When implemented in derived classes, reads current field's value.
*/
public get(): TDefinition["TValue"] {
get(): TDefinition["TValue"] {
return this.value;
}
/**
* When implemented in derived classes, updates current field's value.
*/
public set(value: TDefinition["TValue"]): void {
set(value: TDefinition["TValue"]): void {
this.value = value;
}
/**
* When implemented in derived classes, serializes this field into `dataView` at `offset`
*/
public abstract serialize(dataView: DataView, offset: number): void;
abstract serialize(dataView: DataView, offset: number): void;
}

View file

@ -3,7 +3,7 @@ import type { ValueOrPromise } from "../utils.js";
// TODO: allow over reading (returning a `Uint8Array`, an `offset` and a `length`) to avoid copying
export class ExactReadableEndedError extends Error {
public constructor() {
constructor() {
super("ExactReadable ended");
Object.setPrototypeOf(this, new.target.prototype);
}

View file

@ -14,9 +14,9 @@ export class StructValue {
/**
* Gets the result struct value object
*/
public readonly value: Record<PropertyKey, unknown>;
readonly value: Record<PropertyKey, unknown>;
public constructor(prototype: object) {
constructor(prototype: object) {
// PERF: `Object.create(extra)` is 50% faster
// than `Object.defineProperties(this.value, extra)`
this.value = Object.create(prototype) as Record<PropertyKey, unknown>;
@ -35,7 +35,7 @@ export class StructValue {
* @param name The field name
* @param fieldValue The associated `StructFieldValue`
*/
public set(name: PropertyKey, fieldValue: StructFieldValue): void {
set(name: PropertyKey, fieldValue: StructFieldValue): void {
this.fieldValues[name] = fieldValue;
// PERF: `Object.defineProperty` is slow
@ -61,7 +61,7 @@ export class StructValue {
*
* @param name The field name
*/
public get(name: PropertyKey): StructFieldValue {
get(name: PropertyKey): StructFieldValue {
return this.fieldValues[name]!;
}
}

View file

@ -22,11 +22,11 @@ import {
} from "./index.js";
class MockDeserializationStream implements ExactReadable {
public buffer = new Uint8Array(0);
buffer = new Uint8Array(0);
public position = 0;
position = 0;
public readExactly = jest.fn(() => this.buffer);
readExactly = jest.fn(() => this.buffer);
}
describe("Struct", () => {
@ -40,15 +40,15 @@ describe("Struct", () => {
describe("#field", () => {
class MockFieldDefinition extends StructFieldDefinition<number> {
public constructor(size: number) {
constructor(size: number) {
super(size);
}
public getSize = jest.fn(() => {
getSize = jest.fn(() => {
return this.options;
});
public create(
create(
options: Readonly<StructOptions>,
struct: StructValue,
value: unknown,
@ -58,17 +58,17 @@ describe("Struct", () => {
void value;
throw new Error("Method not implemented.");
}
public override deserialize(
override deserialize(
options: Readonly<StructOptions>,
stream: ExactReadable,
struct: StructValue,
): StructFieldValue<this>;
public override deserialize(
override deserialize(
options: Readonly<StructOptions>,
stream: AsyncExactReadable,
struct: StructValue,
): Promise<StructFieldValue<this>>;
public override deserialize(
override deserialize(
options: Readonly<StructOptions>,
stream: ExactReadable | AsyncExactReadable,
struct: StructValue,

View file

@ -192,14 +192,14 @@ export type StructDeserializedResult<
: TPostDeserialized;
export class StructDeserializeError extends Error {
public constructor(message: string) {
constructor(message: string) {
super(message);
Object.setPrototypeOf(this, new.target.prototype);
}
}
export class StructNotEnoughDataError extends StructDeserializeError {
public constructor() {
constructor() {
super(
"The underlying readable was ended before the struct was fully deserialized",
);
@ -207,7 +207,7 @@ export class StructNotEnoughDataError extends StructDeserializeError {
}
export class StructEmptyError extends StructDeserializeError {
public constructor() {
constructor() {
super("The underlying readable doesn't contain any more struct");
}
}
@ -222,27 +222,27 @@ export class Struct<
StructDeserializedResult<TFields, TExtra, TPostDeserialized>
>
{
public readonly TFields!: TFields;
readonly TFields!: TFields;
public readonly TOmitInitKey!: TOmitInitKey;
readonly TOmitInitKey!: TOmitInitKey;
public readonly TExtra!: TExtra;
readonly TExtra!: TExtra;
public readonly TInit!: Evaluate<Omit<TFields, TOmitInitKey>>;
readonly TInit!: Evaluate<Omit<TFields, TOmitInitKey>>;
public readonly TDeserializeResult!: StructDeserializedResult<
readonly TDeserializeResult!: StructDeserializedResult<
TFields,
TExtra,
TPostDeserialized
>;
public readonly options: Readonly<StructOptions>;
readonly options: Readonly<StructOptions>;
#size = 0;
/**
* Gets the static size (exclude fields that can change size at runtime)
*/
public get size() {
get size() {
return this.#size;
}
@ -250,7 +250,7 @@ export class Struct<
name: PropertyKey,
definition: StructFieldDefinition<any, any, any>,
][] = [];
public get fields(): readonly [
get fields(): readonly [
name: PropertyKey,
definition: StructFieldDefinition<any, any, any>,
][] {
@ -261,14 +261,14 @@ export class Struct<
#postDeserialized?: StructPostDeserialized<any, any> | undefined;
public constructor(options?: Partial<Readonly<StructOptions>>) {
constructor(options?: Partial<Readonly<StructOptions>>) {
this.options = { ...StructDefaultOptions, ...options };
}
/**
* Appends a `StructFieldDefinition` to the `Struct
*/
public field<
field<
TName extends PropertyKey,
TDefinition extends StructFieldDefinition<any, any, any>,
>(
@ -304,7 +304,7 @@ export class Struct<
/**
* Merges (flats) another `Struct`'s fields and extra fields into this one.
*/
public concat<TOther extends Struct<any, any, any, any>>(
concat<TOther extends Struct<any, any, any, any>>(
other: TOther,
): Struct<
TFields & TOther["TFields"],
@ -323,7 +323,7 @@ export class Struct<
return this as any;
}
private number<
#number<
TName extends PropertyKey,
TType extends NumberFieldType = NumberFieldType,
TTypeScriptType = number,
@ -337,64 +337,64 @@ export class Struct<
/**
* Appends an `int8` field to the `Struct`
*/
public int8<TName extends PropertyKey, TTypeScriptType = number>(
int8<TName extends PropertyKey, TTypeScriptType = number>(
name: TName,
typeScriptType?: TTypeScriptType,
) {
return this.number(name, NumberFieldType.Int8, typeScriptType);
return this.#number(name, NumberFieldType.Int8, typeScriptType);
}
/**
* Appends an `uint8` field to the `Struct`
*/
public uint8<TName extends PropertyKey, TTypeScriptType = number>(
uint8<TName extends PropertyKey, TTypeScriptType = number>(
name: TName,
typeScriptType?: TTypeScriptType,
) {
return this.number(name, NumberFieldType.Uint8, typeScriptType);
return this.#number(name, NumberFieldType.Uint8, typeScriptType);
}
/**
* Appends an `int16` field to the `Struct`
*/
public int16<TName extends PropertyKey, TTypeScriptType = number>(
int16<TName extends PropertyKey, TTypeScriptType = number>(
name: TName,
typeScriptType?: TTypeScriptType,
) {
return this.number(name, NumberFieldType.Int16, typeScriptType);
return this.#number(name, NumberFieldType.Int16, typeScriptType);
}
/**
* Appends an `uint16` field to the `Struct`
*/
public uint16<TName extends PropertyKey, TTypeScriptType = number>(
uint16<TName extends PropertyKey, TTypeScriptType = number>(
name: TName,
typeScriptType?: TTypeScriptType,
) {
return this.number(name, NumberFieldType.Uint16, typeScriptType);
return this.#number(name, NumberFieldType.Uint16, typeScriptType);
}
/**
* Appends an `int32` field to the `Struct`
*/
public int32<TName extends PropertyKey, TTypeScriptType = number>(
int32<TName extends PropertyKey, TTypeScriptType = number>(
name: TName,
typeScriptType?: TTypeScriptType,
) {
return this.number(name, NumberFieldType.Int32, typeScriptType);
return this.#number(name, NumberFieldType.Int32, typeScriptType);
}
/**
* Appends an `uint32` field to the `Struct`
*/
public uint32<TName extends PropertyKey, TTypeScriptType = number>(
uint32<TName extends PropertyKey, TTypeScriptType = number>(
name: TName,
typeScriptType?: TTypeScriptType,
) {
return this.number(name, NumberFieldType.Uint32, typeScriptType);
return this.#number(name, NumberFieldType.Uint32, typeScriptType);
}
private bigint<
#bigint<
TName extends PropertyKey,
TType extends BigIntFieldType = BigIntFieldType,
TTypeScriptType = TType["TTypeScriptType"],
@ -410,11 +410,11 @@ export class Struct<
*
* Requires native `BigInt` support
*/
public int64<
int64<
TName extends PropertyKey,
TTypeScriptType = BigIntFieldType["TTypeScriptType"],
>(name: TName, typeScriptType?: TTypeScriptType) {
return this.bigint(name, BigIntFieldType.Int64, typeScriptType);
return this.#bigint(name, BigIntFieldType.Int64, typeScriptType);
}
/**
@ -422,14 +422,14 @@ export class Struct<
*
* Requires native `BigInt` support
*/
public uint64<
uint64<
TName extends PropertyKey,
TTypeScriptType = BigIntFieldType["TTypeScriptType"],
>(name: TName, typeScriptType?: TTypeScriptType) {
return this.bigint(name, BigIntFieldType.Uint64, typeScriptType);
return this.#bigint(name, BigIntFieldType.Uint64, typeScriptType);
}
private arrayBufferLike: ArrayBufferLikeFieldCreator<
#arrayBufferLike: ArrayBufferLikeFieldCreator<
TFields,
TOmitInitKey,
TExtra,
@ -454,14 +454,14 @@ export class Struct<
}
};
public uint8Array: BoundArrayBufferLikeFieldDefinitionCreator<
uint8Array: BoundArrayBufferLikeFieldDefinitionCreator<
TFields,
TOmitInitKey,
TExtra,
TPostDeserialized,
Uint8ArrayBufferFieldSubType
> = (name: PropertyKey, options: any, typeScriptType: any): any => {
return this.arrayBufferLike(
return this.#arrayBufferLike(
name,
Uint8ArrayBufferFieldSubType.Instance,
options,
@ -469,14 +469,14 @@ export class Struct<
);
};
public string: BoundArrayBufferLikeFieldDefinitionCreator<
string: BoundArrayBufferLikeFieldDefinitionCreator<
TFields,
TOmitInitKey,
TExtra,
TPostDeserialized,
StringBufferFieldSubType
> = (name: PropertyKey, options: any, typeScriptType: any): any => {
return this.arrayBufferLike(
return this.#arrayBufferLike(
name,
StringBufferFieldSubType.Instance,
options,
@ -494,7 +494,7 @@ export class Struct<
* @param value
* An object containing properties to be added to the result value. Accessors and methods are also allowed.
*/
public extra<
extra<
T extends Record<
// This trick disallows any keys that are already in `TValue`
Exclude<keyof T, Exclude<keyof T, keyof TFields>>,
@ -516,7 +516,7 @@ export class Struct<
* A callback returning `never` (always throw an error)
* will also change the return type of `deserialize` to `never`.
*/
public postDeserialize(
postDeserialize(
callback: StructPostDeserialized<TFields, never>,
): Struct<TFields, TOmitInitKey, TExtra, never>;
/**
@ -525,7 +525,7 @@ export class Struct<
* A callback returning `void` means it modify the result object in-place
* (or doesn't modify it at all), so `deserialize` will still return the result object.
*/
public postDeserialize(
postDeserialize(
callback?: StructPostDeserialized<TFields, void>,
): Struct<TFields, TOmitInitKey, TExtra, undefined>;
/**
@ -534,10 +534,10 @@ export class Struct<
* A callback returning anything other than `undefined`
* will `deserialize` to return that object instead.
*/
public postDeserialize<TPostSerialize>(
postDeserialize<TPostSerialize>(
callback?: StructPostDeserialized<TFields, TPostSerialize>,
): Struct<TFields, TOmitInitKey, TExtra, TPostSerialize>;
public postDeserialize(callback?: StructPostDeserialized<TFields, any>) {
postDeserialize(callback?: StructPostDeserialized<TFields, any>) {
this.#postDeserialized = callback;
return this as any;
}
@ -545,13 +545,13 @@ export class Struct<
/**
* Deserialize a struct value from `stream`.
*/
public deserialize(
deserialize(
stream: ExactReadable,
): StructDeserializedResult<TFields, TExtra, TPostDeserialized>;
public deserialize(
deserialize(
stream: AsyncExactReadable,
): Promise<StructDeserializedResult<TFields, TExtra, TPostDeserialized>>;
public deserialize(
deserialize(
stream: ExactReadable | AsyncExactReadable,
): ValueOrPromise<
StructDeserializedResult<TFields, TExtra, TPostDeserialized>
@ -603,12 +603,12 @@ export class Struct<
.valueOrPromise();
}
public serialize(init: Evaluate<Omit<TFields, TOmitInitKey>>): Uint8Array;
public serialize(
serialize(init: Evaluate<Omit<TFields, TOmitInitKey>>): Uint8Array;
serialize(
init: Evaluate<Omit<TFields, TOmitInitKey>>,
output: Uint8Array,
): number;
public serialize(
serialize(
init: Evaluate<Omit<TFields, TOmitInitKey>>,
output?: Uint8Array,
): Uint8Array | number {

View file

@ -57,11 +57,11 @@ export const SyncPromise: SyncPromiseStatic = {
class PendingSyncPromise<T> implements SyncPromise<T> {
#promise: PromiseLike<T>;
public constructor(promise: PromiseLike<T>) {
constructor(promise: PromiseLike<T>) {
this.#promise = promise;
}
public then<TResult1 = T, TResult2 = never>(
then<TResult1 = T, TResult2 = never>(
onfulfilled?:
| ((value: T) => TResult1 | PromiseLike<TResult1>)
| null
@ -76,7 +76,7 @@ class PendingSyncPromise<T> implements SyncPromise<T> {
);
}
public valueOrPromise(): T | PromiseLike<T> {
valueOrPromise(): T | PromiseLike<T> {
return this.#promise;
}
}
@ -84,11 +84,11 @@ class PendingSyncPromise<T> implements SyncPromise<T> {
class ResolvedSyncPromise<T> implements SyncPromise<T> {
#value: T;
public constructor(value: T) {
constructor(value: T) {
this.#value = value;
}
public then<TResult1 = T>(
then<TResult1 = T>(
onfulfilled?:
| ((value: T) => TResult1 | PromiseLike<TResult1>)
| null
@ -100,7 +100,7 @@ class ResolvedSyncPromise<T> implements SyncPromise<T> {
return SyncPromise.try(() => onfulfilled(this.#value));
}
public valueOrPromise(): T | PromiseLike<T> {
valueOrPromise(): T | PromiseLike<T> {
return this.#value;
}
}
@ -108,11 +108,11 @@ class ResolvedSyncPromise<T> implements SyncPromise<T> {
class RejectedSyncPromise<T> implements SyncPromise<T> {
#reason: any;
public constructor(reason: any) {
constructor(reason: any) {
this.#reason = reason;
}
public then<TResult1 = T, TResult2 = never>(
then<TResult1 = T, TResult2 = never>(
onfulfilled?:
| ((value: T) => TResult1 | PromiseLike<TResult1>)
| null
@ -128,7 +128,7 @@ class RejectedSyncPromise<T> implements SyncPromise<T> {
return SyncPromise.try(() => onrejected(this.#reason));
}
public valueOrPromise(): T | PromiseLike<T> {
valueOrPromise(): T | PromiseLike<T> {
throw this.#reason;
}
}

Some files were not shown because too many files have changed in this diff Show more