diff --git a/.changeset/quiet-ghosts-think.md b/.changeset/quiet-ghosts-think.md new file mode 100644 index 000000000..41739de56 --- /dev/null +++ b/.changeset/quiet-ghosts-think.md @@ -0,0 +1,5 @@ +--- +"@yume-chan/scrcpy": minor +--- + +Add support for Scrcpy 3.1 diff --git a/libraries/scrcpy/src/3_1/impl/defaults.ts b/libraries/scrcpy/src/3_1/impl/defaults.ts new file mode 100644 index 000000000..5869af352 --- /dev/null +++ b/libraries/scrcpy/src/3_1/impl/defaults.ts @@ -0,0 +1,8 @@ +import type { Init } from "./init.js"; +import { PrevImpl } from "./prev.js"; + +export const Defaults = /* #__PURE__ */ (() => + ({ + ...PrevImpl.Defaults, + vdDestroyContent: false, + }) as const satisfies Required)(); diff --git a/libraries/scrcpy/src/3_1/impl/index.ts b/libraries/scrcpy/src/3_1/impl/index.ts new file mode 100644 index 000000000..15facd5dc --- /dev/null +++ b/libraries/scrcpy/src/3_1/impl/index.ts @@ -0,0 +1,7 @@ +export * from "../../3_0/impl/index.js"; +export { Defaults } from "./defaults.js"; +export type { Init } from "./init.js"; +export { + serializeUHidCreateControlMessage, + UHidCreateControlMessage, +} from "./serialize-uhid-create.js"; diff --git a/libraries/scrcpy/src/3_1/impl/init.ts b/libraries/scrcpy/src/3_1/impl/init.ts new file mode 100644 index 000000000..79b03abf3 --- /dev/null +++ b/libraries/scrcpy/src/3_1/impl/init.ts @@ -0,0 +1,5 @@ +import type { PrevImpl } from "./prev.js"; + +export interface Init extends PrevImpl.Init { + vdDestroyContent?: boolean; +} diff --git a/libraries/scrcpy/src/3_1/impl/prev.ts b/libraries/scrcpy/src/3_1/impl/prev.ts new file mode 100644 index 000000000..9a50c8cdd --- /dev/null +++ b/libraries/scrcpy/src/3_1/impl/prev.ts @@ -0,0 +1 @@ +export * as PrevImpl from "../../3_0/impl/index.js"; diff --git a/libraries/scrcpy/src/3_1/impl/serialize-uhid-create.ts b/libraries/scrcpy/src/3_1/impl/serialize-uhid-create.ts new file mode 100644 index 000000000..8799c706d --- /dev/null +++ b/libraries/scrcpy/src/3_1/impl/serialize-uhid-create.ts @@ -0,0 +1,28 @@ +import type { StructInit } from "@yume-chan/struct"; +import { buffer, string, struct, u16, u8 } from "@yume-chan/struct"; + +import { ScrcpyControlMessageType } from "../../base/control-message-type.js"; +import type { ScrcpyUHidCreateControlMessage } from "../../latest.js"; + +export const UHidCreateControlMessage = /* #__PURE__ */ (() => + struct( + { + type: u8(ScrcpyControlMessageType.UHidCreate), + id: u16, + vendorId: u16, + productId: u16, + name: string(u8), + data: buffer(u16), + }, + { littleEndian: false }, + ))(); + +export type UHidCreateControlMessage = StructInit< + typeof UHidCreateControlMessage +>; + +export function serializeUHidCreateControlMessage( + message: ScrcpyUHidCreateControlMessage, +) { + return UHidCreateControlMessage.serialize(message); +} diff --git a/libraries/scrcpy/src/3_1/index.ts b/libraries/scrcpy/src/3_1/index.ts new file mode 100644 index 000000000..38426e545 --- /dev/null +++ b/libraries/scrcpy/src/3_1/index.ts @@ -0,0 +1,2 @@ +export * as ScrcpyOptions3_1Impl from "./impl/index.js"; +export * from "./options.js"; diff --git a/libraries/scrcpy/src/3_1/options.ts b/libraries/scrcpy/src/3_1/options.ts new file mode 100644 index 000000000..288ac623e --- /dev/null +++ b/libraries/scrcpy/src/3_1/options.ts @@ -0,0 +1,190 @@ +import type { MaybePromiseLike } from "@yume-chan/async"; +import type { ReadableStream, TransformStream } from "@yume-chan/stream-extra"; +import type { AsyncExactReadable } from "@yume-chan/struct"; + +import type { + ScrcpyAudioStreamMetadata, + ScrcpyControlMessageType, + ScrcpyDisplay, + ScrcpyEncoder, + ScrcpyMediaStreamPacket, + ScrcpyOptions, + ScrcpyScrollController, + ScrcpyVideoStream, +} from "../base/index.js"; +import type { + ScrcpyBackOrScreenOnControlMessage, + ScrcpyInjectTouchControlMessage, + ScrcpySetClipboardControlMessage, + ScrcpyUHidCreateControlMessage, + ScrcpyUHidOutputDeviceMessage, +} from "../latest.js"; + +import type { Init } from "./impl/index.js"; +import { + AckClipboardHandler, + ClipboardStream, + ControlMessageTypes, + createMediaStreamTransformer, + createScrollController, + Defaults, + parseAudioStreamMetadata, + parseDisplay, + parseEncoder, + parseVideoStreamMetadata, + serialize, + serializeBackOrScreenOnControlMessage, + serializeInjectTouchControlMessage, + serializeUHidCreateControlMessage, + setListDisplays, + setListEncoders, + UHidOutputStream, +} from "./impl/index.js"; + +export class ScrcpyOptions3_1 implements ScrcpyOptions { + static readonly Defaults = Defaults; + + readonly version: string; + + readonly value: Required; + + get controlMessageTypes(): readonly ScrcpyControlMessageType[] { + return ControlMessageTypes; + } + + #clipboard: ClipboardStream | undefined; + get clipboard(): ReadableStream | undefined { + return this.#clipboard; + } + + #ackClipboardHandler: AckClipboardHandler | undefined; + + #uHidOutput: UHidOutputStream | undefined; + get uHidOutput(): + | ReadableStream + | undefined { + return this.#uHidOutput; + } + + constructor(init: Init, version = "3.1") { + this.value = { ...Defaults, ...init }; + this.version = version; + + if (this.value.videoSource === "camera") { + this.value.control = false; + } + + if (this.value.audioDup) { + this.value.audioSource = "playback"; + } + + if (this.value.control) { + if (this.value.clipboardAutosync) { + this.#clipboard = new ClipboardStream(); + this.#ackClipboardHandler = new AckClipboardHandler(); + } + + this.#uHidOutput = new UHidOutputStream(); + } + } + + serialize(): string[] { + return serialize(this.value, Defaults); + } + + setListDisplays(): void { + setListDisplays(this.value); + } + + parseDisplay(line: string): ScrcpyDisplay | undefined { + return parseDisplay(line); + } + + setListEncoders() { + setListEncoders(this.value); + } + + parseEncoder(line: string): ScrcpyEncoder | undefined { + return parseEncoder(line); + } + + parseVideoStreamMetadata( + stream: ReadableStream, + ): MaybePromiseLike { + return parseVideoStreamMetadata(this.value, stream); + } + + parseAudioStreamMetadata( + stream: ReadableStream, + ): MaybePromiseLike { + return parseAudioStreamMetadata(stream, this.value); + } + + async parseDeviceMessage( + id: number, + stream: AsyncExactReadable, + ): Promise { + if (await this.#clipboard?.parse(id, stream)) { + return; + } + + if (await this.#ackClipboardHandler?.parse(id, stream)) { + return; + } + + throw new Error("Unknown device message"); + } + + endDeviceMessageStream(e?: unknown): void { + if (e) { + this.#clipboard?.error(e); + this.#ackClipboardHandler?.error(e); + } else { + this.#clipboard?.close(); + this.#ackClipboardHandler?.close(); + } + } + + createMediaStreamTransformer(): TransformStream< + Uint8Array, + ScrcpyMediaStreamPacket + > { + return createMediaStreamTransformer(this.value); + } + + serializeInjectTouchControlMessage( + message: ScrcpyInjectTouchControlMessage, + ): Uint8Array { + return serializeInjectTouchControlMessage(message); + } + + serializeBackOrScreenOnControlMessage( + message: ScrcpyBackOrScreenOnControlMessage, + ): Uint8Array | undefined { + return serializeBackOrScreenOnControlMessage(message); + } + + serializeSetClipboardControlMessage( + message: ScrcpySetClipboardControlMessage, + ): Uint8Array | [Uint8Array, Promise] { + return this.#ackClipboardHandler!.serializeSetClipboardControlMessage( + message, + ); + } + + createScrollController(): ScrcpyScrollController { + return createScrollController(); + } + + serializeUHidCreateControlMessage( + message: ScrcpyUHidCreateControlMessage, + ): Uint8Array { + return serializeUHidCreateControlMessage(message); + } +} + +type Init_ = Init; + +export namespace ScrcpyOptions3_1 { + export type Init = Init_; +} diff --git a/libraries/scrcpy/src/latest.ts b/libraries/scrcpy/src/latest.ts index 0114e0a76..7559039d2 100644 --- a/libraries/scrcpy/src/latest.ts +++ b/libraries/scrcpy/src/latest.ts @@ -1,12 +1,12 @@ -import { ScrcpyOptions3_0 } from "./3_0/options.js"; +import { ScrcpyOptions3_1 } from "./3_1/options.js"; -export class ScrcpyOptionsLatest extends ScrcpyOptions3_0 { - constructor(init: ScrcpyOptions3_0.Init, version: string) { +export class ScrcpyOptionsLatest extends ScrcpyOptions3_1 { + constructor(init: ScrcpyOptions3_1.Init, version: string) { super(init, version); } } -export { ScrcpyOptions3_0Impl as ScrcpyOptionsLatestImpl } from "./3_0/index.js"; +export { ScrcpyOptions3_1Impl as ScrcpyOptionsLatestImpl } from "./3_1/index.js"; export { BackOrScreenOnControlMessage as ScrcpyBackOrScreenOnControlMessage, @@ -23,4 +23,4 @@ export { SetClipboardControlMessage as ScrcpySetClipboardControlMessage, UHidCreateControlMessage as ScrcpyUHidCreateControlMessage, UHidOutputDeviceMessage as ScrcpyUHidOutputDeviceMessage, -} from "./3_0/impl/index.js"; +} from "./3_1/impl/index.js";