@yume-chan/scrcpy
Version:
TypeScript implementation of Scrcpy.
113 lines (93 loc) • 3.66 kB
text/typescript
// cspell: ignore autosync
import { PromiseResolver } from "@yume-chan/async";
import type { AsyncExactReadable } from "@yume-chan/struct";
import Struct, { placeholder } from "@yume-chan/struct";
import type { ScrcpySetClipboardControlMessage } from "../control/index.js";
import type { ScrcpyOptionsInit1_18 } from "./1_18.js";
import { ScrcpyOptions1_18 } from "./1_18.js";
import { ScrcpyOptions, toScrcpyOptionValue } from "./types.js";
export const ScrcpyAckClipboardDeviceMessage = new Struct().uint64("sequence");
export interface ScrcpyOptionsInit1_21 extends ScrcpyOptionsInit1_18 {
clipboardAutosync?: boolean;
}
function toSnakeCase(input: string): string {
return input.replace(/([A-Z])/g, "_$1").toLowerCase();
}
export const ScrcpySetClipboardControlMessage1_21 = new Struct()
.uint8("type")
.uint64("sequence")
.int8("paste", placeholder<boolean>())
.uint32("length")
.string("content", { lengthField: "length" });
export type ScrcpySetClipboardControlMessage1_21 =
(typeof ScrcpySetClipboardControlMessage1_21)["TInit"];
export class ScrcpyOptions1_21 extends ScrcpyOptions<ScrcpyOptionsInit1_21> {
static readonly DEFAULTS = {
...ScrcpyOptions1_18.DEFAULTS,
clipboardAutosync: true,
} as const satisfies Required<ScrcpyOptionsInit1_21>;
static serialize<T extends object>(
options: T,
defaults: Required<T>,
): string[] {
// 1.21 changed the format of arguments
const result: string[] = [];
for (const [key, value] of Object.entries(options)) {
const serializedValue = toScrcpyOptionValue(value, undefined);
if (!serializedValue) {
continue;
}
const defaultValue = toScrcpyOptionValue(
defaults[key as keyof T],
undefined,
);
if (serializedValue == defaultValue) {
continue;
}
result.push(`${toSnakeCase(key)}=${serializedValue}`);
}
return result;
}
override get defaults(): Required<ScrcpyOptionsInit1_21> {
return ScrcpyOptions1_21.DEFAULTS;
}
#clipboardAck = new Map<bigint, PromiseResolver<void>>();
constructor(init: ScrcpyOptionsInit1_21) {
super(ScrcpyOptions1_18, init, ScrcpyOptions1_21.DEFAULTS);
}
override async parseDeviceMessage(
id: number,
stream: AsyncExactReadable,
): Promise<boolean> {
switch (id) {
case 1: {
const message =
await ScrcpyAckClipboardDeviceMessage.deserialize(stream);
const resolver = this.#clipboardAck.get(message.sequence);
if (resolver) {
resolver.resolve();
this.#clipboardAck.delete(message.sequence);
}
return true;
}
default:
return await super.parseDeviceMessage(id, stream);
}
}
override serialize(): string[] {
return ScrcpyOptions1_21.serialize(this.value, this.defaults);
}
override serializeSetClipboardControlMessage(
message: ScrcpySetClipboardControlMessage,
): Uint8Array | [Uint8Array, Promise<void>] {
if (message.sequence === 0n) {
return ScrcpySetClipboardControlMessage1_21.serialize(message);
}
const resolver = new PromiseResolver<void>();
this.#clipboardAck.set(message.sequence, resolver);
return [
ScrcpySetClipboardControlMessage1_21.serialize(message),
resolver.promise,
];
}
}