diff --git a/libraries/adb-scrcpy/src/options/1_16.ts b/libraries/adb-scrcpy/src/options/1_16.ts index b22789b4..e8e55aad 100644 --- a/libraries/adb-scrcpy/src/options/1_16.ts +++ b/libraries/adb-scrcpy/src/options/1_16.ts @@ -46,6 +46,8 @@ export class AdbScrcpyOptions1_16 extends AdbScrcpyOptions< const client = await AdbScrcpyClient.start(adb, path, version, options); const encoders: ScrcpyEncoder[] = []; + + // `client.stdout` is supplied by user and may not support async iteration await client.stdout.pipeTo( new WritableStream({ write: (line) => { diff --git a/libraries/adb/src/commands/subprocess/protocols/none.ts b/libraries/adb/src/commands/subprocess/protocols/none.ts index 3ad57522..bf6f9a68 100644 --- a/libraries/adb/src/commands/subprocess/protocols/none.ts +++ b/libraries/adb/src/commands/subprocess/protocols/none.ts @@ -2,7 +2,6 @@ import type { MaybeConsumable, WritableStream } from "@yume-chan/stream-extra"; import { ReadableStream } from "@yume-chan/stream-extra"; import type { Adb, AdbSocket } from "../../../adb.js"; -import { unreachable } from "../../../utils/index.js"; import type { AdbSubprocessProtocol } from "./types.js"; @@ -64,10 +63,9 @@ export class AdbSubprocessNoneProtocol implements AdbSubprocessProtocol { this.#socket = socket; this.#stderr = new ReadableStream({ - start: (controller) => { - this.#socket.closed - .then(() => controller.close()) - .catch(unreachable); + start: async (controller) => { + await this.#socket.closed; + controller.close(); }, }); this.#exit = socket.closed.then(() => 0); diff --git a/libraries/adb/src/commands/sync/list.ts b/libraries/adb/src/commands/sync/list.ts index 9d0f27a5..f3ee29ec 100644 --- a/libraries/adb/src/commands/sync/list.ts +++ b/libraries/adb/src/commands/sync/list.ts @@ -15,23 +15,25 @@ export interface AdbSyncEntry extends AdbSyncStat { name: string; } -export const AdbSyncEntryResponse = struct( - /* #__PURE__ */ (() => ({ - ...AdbSyncLstatResponse.fields, - name: string(u32), - }))(), - { littleEndian: true, extra: AdbSyncLstatResponse.extra }, -); +export const AdbSyncEntryResponse = /* #__PURE__ */ (() => + struct( + { + ...AdbSyncLstatResponse.fields, + name: string(u32), + }, + { littleEndian: true, extra: AdbSyncLstatResponse.extra }, + ))(); export type AdbSyncEntryResponse = StructValue; -export const AdbSyncEntry2Response = struct( - /* #__PURE__ */ (() => ({ - ...AdbSyncStatResponse.fields, - name: string(u32), - }))(), - { littleEndian: true, extra: AdbSyncStatResponse.extra }, -); +export const AdbSyncEntry2Response = /* #__PURE__ */ (() => + struct( + { + ...AdbSyncStatResponse.fields, + name: string(u32), + }, + { littleEndian: true, extra: AdbSyncStatResponse.extra }, + ))(); export type AdbSyncEntry2Response = StructValue; diff --git a/libraries/adb/src/daemon/auth.ts b/libraries/adb/src/daemon/auth.ts index 202e9135..a76060a4 100644 --- a/libraries/adb/src/daemon/auth.ts +++ b/libraries/adb/src/daemon/auth.ts @@ -43,11 +43,13 @@ export interface AdbCredentialStore { iterateKeys(): AdbKeyIterable; } -export enum AdbAuthType { - Token = 1, - Signature = 2, - PublicKey = 3, -} +export const AdbAuthType = { + Token: 1, + Signature: 2, + PublicKey: 3, +} as const; + +export type AdbAuthType = (typeof AdbAuthType)[keyof typeof AdbAuthType]; export interface AdbAuthenticator { /** diff --git a/libraries/adb/src/daemon/transport.ts b/libraries/adb/src/daemon/transport.ts index b57796aa..e98dd691 100644 --- a/libraries/adb/src/daemon/transport.ts +++ b/libraries/adb/src/daemon/transport.ts @@ -28,27 +28,28 @@ import { AdbCommand, calculateChecksum } from "./packet.js"; export const ADB_DAEMON_VERSION_OMIT_CHECKSUM = 0x01000001; // https://android.googlesource.com/platform/packages/modules/adb/+/79010dc6d5ca7490c493df800d4421730f5466ca/transport.cpp#1252 // There are some other feature constants, but some of them are only used by ADB server, not devices (daemons). -export const ADB_DAEMON_DEFAULT_FEATURES = [ - AdbFeature.ShellV2, - AdbFeature.Cmd, - AdbFeature.StatV2, - AdbFeature.ListV2, - AdbFeature.FixedPushMkdir, - "apex", - AdbFeature.Abb, - // only tells the client the symlink timestamp issue in `adb push --sync` has been fixed. - // No special handling required. - "fixed_push_symlink_timestamp", - AdbFeature.AbbExec, - "remount_shell", - "track_app", - AdbFeature.SendReceiveV2, - "sendrecv_v2_brotli", - "sendrecv_v2_lz4", - "sendrecv_v2_zstd", - "sendrecv_v2_dry_run_send", - AdbFeature.DelayedAck, -] as AdbFeature[]; +export const ADB_DAEMON_DEFAULT_FEATURES = /* #__PURE__ */ (() => + [ + AdbFeature.ShellV2, + AdbFeature.Cmd, + AdbFeature.StatV2, + AdbFeature.ListV2, + AdbFeature.FixedPushMkdir, + "apex", + AdbFeature.Abb, + // only tells the client the symlink timestamp issue in `adb push --sync` has been fixed. + // No special handling required. + "fixed_push_symlink_timestamp", + AdbFeature.AbbExec, + "remount_shell", + "track_app", + AdbFeature.SendReceiveV2, + "sendrecv_v2_brotli", + "sendrecv_v2_lz4", + "sendrecv_v2_zstd", + "sendrecv_v2_dry_run_send", + AdbFeature.DelayedAck, + ] as AdbFeature[])(); export const ADB_DAEMON_DEFAULT_INITIAL_PAYLOAD_SIZE = 32 * 1024 * 1024; export type AdbDaemonConnection = ReadableWritablePair< diff --git a/libraries/android-bin/src/bug-report.ts b/libraries/android-bin/src/bug-report.ts index dafc3763..e6fda2fc 100644 --- a/libraries/android-bin/src/bug-report.ts +++ b/libraries/android-bin/src/bug-report.ts @@ -211,37 +211,33 @@ export class BugReport extends AdbCommandBase { let filename: string | undefined; let error: string | undefined; - await process.stdout + for await (const line of process.stdout .pipeThrough(new TextDecoderStream()) - .pipeThrough(new SplitStringStream("\n")) - .pipeTo( - new WritableStream({ - write(line) { - // `BEGIN:` and `PROGRESS:` only appear when `-p` is specified. - let match = line.match(BugReport.PROGRESS_REGEX); - if (match) { - options?.onProgress?.(match[1]!, match[2]!); - } + // Each chunk should contain one or several full lines + .pipeThrough(new SplitStringStream("\n"))) { + // `BEGIN:` and `PROGRESS:` only appear when `-p` is specified. + let match = line.match(BugReport.PROGRESS_REGEX); + if (match) { + options?.onProgress?.(match[1]!, match[2]!); + } - match = line.match(BugReport.BEGIN_REGEX); - if (match) { - filename = match[1]!; - } + match = line.match(BugReport.BEGIN_REGEX); + if (match) { + filename = match[1]!; + } - match = line.match(BugReport.OK_REGEX); - if (match) { - filename = match[1]; - } + match = line.match(BugReport.OK_REGEX); + if (match) { + filename = match[1]; + } - match = line.match(BugReport.FAIL_REGEX); - if (match) { - // Don't report error now - // We want to gather all output. - error = match[1]; - } - }, - }), - ); + match = line.match(BugReport.FAIL_REGEX); + if (match) { + // Don't report error now + // We want to gather all output. + error = match[1]; + } + } if (error) { throw new Error(error); diff --git a/libraries/android-bin/src/logcat.ts b/libraries/android-bin/src/logcat.ts index 699a186c..5c1eec24 100644 --- a/libraries/android-bin/src/logcat.ts +++ b/libraries/android-bin/src/logcat.ts @@ -8,7 +8,6 @@ import { SplitStringStream, TextDecoderStream, WrapReadableStream, - WritableStream, } from "@yume-chan/stream-extra"; import type { AsyncExactReadable, StructValue } from "@yume-chan/struct"; import { decodeUtf8, struct, u16, u32 } from "@yume-chan/struct"; @@ -437,53 +436,48 @@ export class Logcat extends AdbCommandBase { ]); const result: LogSize[] = []; - await stdout + for await (const line of stdout .pipeThrough(new TextDecoderStream()) - .pipeThrough(new SplitStringStream("\n")) - .pipeTo( - new WritableStream({ - write(chunk) { - let match = chunk.match(Logcat.LOG_SIZE_REGEX_11); - if (match) { - result.push({ - id: Logcat.logNameToId(match[1]!), - size: Logcat.parseSize( - Number.parseInt(match[2]!, 10), - match[3]!, - ), - readable: Logcat.parseSize( - Number.parseInt(match[6]!, 10), - match[7]!, - ), - consumed: Logcat.parseSize( - Number.parseInt(match[4]!, 10), - match[5]!, - ), - maxEntrySize: parseInt(match[8]!, 10), - maxPayloadSize: parseInt(match[9]!, 10), - }); - return; - } + .pipeThrough(new SplitStringStream("\n"))) { + let match = line.match(Logcat.LOG_SIZE_REGEX_11); + if (match) { + result.push({ + id: Logcat.logNameToId(match[1]!), + size: Logcat.parseSize( + Number.parseInt(match[2]!, 10), + match[3]!, + ), + readable: Logcat.parseSize( + Number.parseInt(match[6]!, 10), + match[7]!, + ), + consumed: Logcat.parseSize( + Number.parseInt(match[4]!, 10), + match[5]!, + ), + maxEntrySize: parseInt(match[8]!, 10), + maxPayloadSize: parseInt(match[9]!, 10), + }); + break; + } - match = chunk.match(Logcat.LOG_SIZE_REGEX_10); - if (match) { - result.push({ - id: Logcat.logNameToId(match[1]!), - size: Logcat.parseSize( - Number.parseInt(match[2]!, 10), - match[3]!, - ), - consumed: Logcat.parseSize( - Number.parseInt(match[4]!, 10), - match[5]!, - ), - maxEntrySize: parseInt(match[6]!, 10), - maxPayloadSize: parseInt(match[7]!, 10), - }); - } - }, - }), - ); + match = line.match(Logcat.LOG_SIZE_REGEX_10); + if (match) { + result.push({ + id: Logcat.logNameToId(match[1]!), + size: Logcat.parseSize( + Number.parseInt(match[2]!, 10), + match[3]!, + ), + consumed: Logcat.parseSize( + Number.parseInt(match[4]!, 10), + match[5]!, + ), + maxEntrySize: parseInt(match[6]!, 10), + maxPayloadSize: parseInt(match[7]!, 10), + }); + } + } return result; } diff --git a/libraries/scrcpy/side-effect-test/src/index.js b/libraries/scrcpy/side-effect-test/src/index.js deleted file mode 100644 index fa448bc1..00000000 --- a/libraries/scrcpy/side-effect-test/src/index.js +++ /dev/null @@ -1 +0,0 @@ -export * from "../../esm/index"; diff --git a/libraries/scrcpy/src/1_15/impl/init.ts b/libraries/scrcpy/src/1_15/impl/init.ts index 28687509..bd2fd0b6 100644 --- a/libraries/scrcpy/src/1_15/impl/init.ts +++ b/libraries/scrcpy/src/1_15/impl/init.ts @@ -6,7 +6,7 @@ export const VideoOrientation = { Landscape: 1, PortraitFlipped: 2, LandscapeFlipped: 3, -}; +} as const; export type VideoOrientation = (typeof VideoOrientation)[keyof typeof VideoOrientation]; @@ -85,6 +85,10 @@ export class CodecOptions implements ScrcpyOptionValue { } } +export namespace CodecOptions { + export type Init = CodecOptionsInit; +} + export interface Init { logLevel?: LogLevel; diff --git a/libraries/scrcpy/src/1_15/impl/inject-touch.ts b/libraries/scrcpy/src/1_15/impl/inject-touch.ts index e9e7bb25..eca781e9 100644 --- a/libraries/scrcpy/src/1_15/impl/inject-touch.ts +++ b/libraries/scrcpy/src/1_15/impl/inject-touch.ts @@ -4,18 +4,7 @@ import { bipedal, struct, u16, u32, u64, u8 } from "@yume-chan/struct"; import type { AndroidMotionEventAction } from "../../android/index.js"; import type { ScrcpyInjectTouchControlMessage } from "../../latest.js"; - -export function clamp(value: number, min: number, max: number): number { - if (value < min) { - return min; - } - - if (value > max) { - return max; - } - - return value; -} +import { clamp } from "../../utils/index.js"; export const UnsignedFloat: Field = { size: 2, @@ -33,6 +22,13 @@ export const UnsignedFloat: Field = { }), }; +export const PointerId = { + Mouse: -1n, + Finger: -2n, + VirtualMouse: -3n, + VirtualFinger: -4n, +} as const; + export const InjectTouchControlMessage = struct( { type: u8, diff --git a/libraries/scrcpy/src/1_18/impl/index.ts b/libraries/scrcpy/src/1_18/impl/index.ts index 3afe0eda..2cb7ce8e 100644 --- a/libraries/scrcpy/src/1_18/impl/index.ts +++ b/libraries/scrcpy/src/1_18/impl/index.ts @@ -5,6 +5,7 @@ export { } from "./back-or-screen-on.js"; export { ControlMessageTypes } from "./control-message-types.js"; export { Defaults } from "./defaults.js"; -export type { Init } from "./init.js"; +export { VideoOrientation } from "./init.js"; +export type { Init, LogLevel } from "./init.js"; export { EncoderRegex } from "./parse-encoder.js"; export { SerializeOrder } from "./serialize-order.js"; diff --git a/libraries/scrcpy/src/1_18/impl/init.ts b/libraries/scrcpy/src/1_18/impl/init.ts index 51ab7d47..04bc4352 100644 --- a/libraries/scrcpy/src/1_18/impl/init.ts +++ b/libraries/scrcpy/src/1_18/impl/init.ts @@ -9,7 +9,7 @@ export const VideoOrientation = { Landscape: 1, PortraitFlipped: 2, LandscapeFlipped: 3, -}; +} as const; export type VideoOrientation = (typeof VideoOrientation)[keyof typeof VideoOrientation]; diff --git a/libraries/scrcpy/src/1_21/impl/serialize.ts b/libraries/scrcpy/src/1_21/impl/serialize.ts index ea862b3b..eb6c31b3 100644 --- a/libraries/scrcpy/src/1_21/impl/serialize.ts +++ b/libraries/scrcpy/src/1_21/impl/serialize.ts @@ -4,15 +4,16 @@ function toSnakeCase(input: string): string { return input.replace(/([A-Z])/g, "_$1").toLowerCase(); } +// 1.21 changed the format of arguments export function serialize( options: T, defaults: Required, ): 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) { + // v3.0 `new_display` option needs to send empty strings to server + if (serializedValue === undefined) { continue; } @@ -20,7 +21,7 @@ export function serialize( defaults[key as keyof T], undefined, ); - if (serializedValue == defaultValue) { + if (serializedValue === defaultValue) { continue; } diff --git a/libraries/scrcpy/src/1_22/impl/scroll-controller.ts b/libraries/scrcpy/src/1_22/impl/scroll-controller.ts index c315f38c..0530126f 100644 --- a/libraries/scrcpy/src/1_22/impl/scroll-controller.ts +++ b/libraries/scrcpy/src/1_22/impl/scroll-controller.ts @@ -1,6 +1,8 @@ import type { StructInit } from "@yume-chan/struct"; import { s32, struct } from "@yume-chan/struct"; +import type { ScrcpyInjectScrollControlMessage } from "../../latest.js"; + import { PrevImpl } from "./prev.js"; export const InjectScrollControlMessage = /* #__PURE__ */ (() => @@ -18,7 +20,7 @@ export type InjectScrollControlMessage = StructInit< export class ScrollController extends PrevImpl.ScrollController { override serializeScrollMessage( - message: InjectScrollControlMessage, + message: ScrcpyInjectScrollControlMessage, ): Uint8Array | undefined { const processed = this.processMessage(message); if (!processed) { diff --git a/libraries/scrcpy/src/1_24/impl/control-message-types.ts b/libraries/scrcpy/src/1_24/impl/control-message-types.ts deleted file mode 100644 index e69de29b..00000000 diff --git a/libraries/scrcpy/src/1_25/impl/scroll-controller.ts b/libraries/scrcpy/src/1_25/impl/scroll-controller.ts index bbb47e18..6746261e 100644 --- a/libraries/scrcpy/src/1_25/impl/scroll-controller.ts +++ b/libraries/scrcpy/src/1_25/impl/scroll-controller.ts @@ -2,16 +2,16 @@ import { getInt16, setInt16 } from "@yume-chan/no-data-view"; import type { Field, StructInit } from "@yume-chan/struct"; import { bipedal, struct, u16, u32, u8 } from "@yume-chan/struct"; +import { ScrcpyControlMessageType } from "../../base/index.js"; import type { ScrcpyScrollController } from "../../base/index.js"; import type { ScrcpyInjectScrollControlMessage } from "../../latest.js"; - -import { PrevImpl } from "./prev.js"; +import { clamp } from "../../utils/index.js"; export const SignedFloat: Field = { size: 2, serialize(value, { buffer, index, littleEndian }) { // https://github.com/Genymobile/scrcpy/blob/1f138aef41de651668043b32c4effc2d4adbfc44/app/src/util/binary.h#L51 - value = PrevImpl.clamp(value, -1, 1); + value = clamp(value, -1, 1); value = value === 1 ? 0x7fff : value * 0x8000; setInt16(buffer, index, value, littleEndian); }, @@ -23,19 +23,20 @@ export const SignedFloat: Field = { }), }; -export const InjectScrollControlMessage = struct( - { - type: u8, - pointerX: u32, - pointerY: u32, - screenWidth: u16, - screenHeight: u16, - scrollX: SignedFloat, - scrollY: SignedFloat, - buttons: u32, - }, - { littleEndian: false }, -); +export const InjectScrollControlMessage = /* #__PURE__ */ (() => + struct( + { + type: u8(ScrcpyControlMessageType.InjectScroll), + pointerX: u32, + pointerY: u32, + screenWidth: u16, + screenHeight: u16, + scrollX: SignedFloat, + scrollY: SignedFloat, + buttons: u32, + }, + { littleEndian: false }, + ))(); export type InjectScrollControlMessage = StructInit< typeof InjectScrollControlMessage diff --git a/libraries/scrcpy/src/2_0/impl/defaults.ts b/libraries/scrcpy/src/2_0/impl/defaults.ts index 7953497b..29e7ba27 100644 --- a/libraries/scrcpy/src/2_0/impl/defaults.ts +++ b/libraries/scrcpy/src/2_0/impl/defaults.ts @@ -1,10 +1,12 @@ +import { omit } from "../../utils/index.js"; + import type { Init } from "./init.js"; import { InstanceId } from "./init.js"; import { PrevImpl } from "./prev.js"; export const Defaults = /* #__PURE__ */ (() => ({ - ...PrevImpl.Defaults, + ...omit(PrevImpl.Defaults, "bitRate", "codecOptions", "encoderName"), scid: InstanceId.NONE, videoCodec: "h264", diff --git a/libraries/scrcpy/src/2_0/impl/index.ts b/libraries/scrcpy/src/2_0/impl/index.ts index 09c144e4..1e20b7f3 100644 --- a/libraries/scrcpy/src/2_0/impl/index.ts +++ b/libraries/scrcpy/src/2_0/impl/index.ts @@ -6,6 +6,7 @@ export { serializeInjectTouchControlMessage, } from "./inject-touch.js"; export * from "./parse-audio-stream-metadata.js"; +export { parseDisplay } from "./parse-display.js"; export { parseEncoder } from "./parse-encoder.js"; export { parseVideoStreamMetadata } from "./parse-video-stream-metadata.js"; export { setListDisplays } from "./set-list-display.js"; diff --git a/libraries/scrcpy/src/2_0/impl/inject-touch.ts b/libraries/scrcpy/src/2_0/impl/inject-touch.ts index 566f46f0..3f8c75de 100644 --- a/libraries/scrcpy/src/2_0/impl/inject-touch.ts +++ b/libraries/scrcpy/src/2_0/impl/inject-touch.ts @@ -2,25 +2,27 @@ import type { StructInit } from "@yume-chan/struct"; import { struct, u16, u32, u64, u8 } from "@yume-chan/struct"; import type { AndroidMotionEventAction } from "../../android/motion-event.js"; +import { ScrcpyControlMessageType } from "../../base/control-message-type.js"; import type { ScrcpyInjectTouchControlMessage } from "../../latest.js"; import { PrevImpl } from "./prev.js"; -export const InjectTouchControlMessage = struct( - { - type: u8, - action: u8(), - pointerId: u64, - pointerX: u32, - pointerY: u32, - screenWidth: u16, - screenHeight: u16, - pressure: PrevImpl.UnsignedFloat, - actionButton: u32, - buttons: u32, - }, - { littleEndian: false }, -); +export const InjectTouchControlMessage = /* #__PURE__ */ (() => + struct( + { + type: u8(ScrcpyControlMessageType.InjectTouch), + action: u8(), + pointerId: u64, + pointerX: u32, + pointerY: u32, + screenWidth: u16, + screenHeight: u16, + pressure: PrevImpl.UnsignedFloat, + actionButton: u32, + buttons: u32, + }, + { littleEndian: false }, + ))(); export type InjectTouchControlMessage = StructInit< typeof InjectTouchControlMessage diff --git a/libraries/scrcpy/src/2_0/impl/parse-display.ts b/libraries/scrcpy/src/2_0/impl/parse-display.ts index 17025f36..4e3ea89b 100644 --- a/libraries/scrcpy/src/2_0/impl/parse-display.ts +++ b/libraries/scrcpy/src/2_0/impl/parse-display.ts @@ -1,6 +1,8 @@ import type { ScrcpyDisplay } from "../../base/index.js"; export function parseDisplay(line: string): ScrcpyDisplay | undefined { + // The client-side option name is `--display` + // but the server-side option name is always `display_id` const match = line.match(/^\s+--display=(\d+)\s+\(([^)]+)\)$/); if (match) { const display: ScrcpyDisplay = { diff --git a/libraries/scrcpy/src/2_0/impl/parse-video-stream-metadata.ts b/libraries/scrcpy/src/2_0/impl/parse-video-stream-metadata.ts index 3826a42d..cc1c9418 100644 --- a/libraries/scrcpy/src/2_0/impl/parse-video-stream-metadata.ts +++ b/libraries/scrcpy/src/2_0/impl/parse-video-stream-metadata.ts @@ -41,7 +41,7 @@ async function parseAsync( let width: number | undefined; let height: number | undefined; if (options.sendCodecMeta) { - codec = await PrevImpl.readU32(buffered); + codec = (await PrevImpl.readU32(buffered)) as ScrcpyVideoCodecId; width = await PrevImpl.readU32(buffered); height = await PrevImpl.readU32(buffered); } else { @@ -64,9 +64,7 @@ export function parseVideoStreamMetadata( if (!options.sendDeviceMeta && !options.sendCodecMeta) { return { stream, - metadata: { - codec: toCodecId(options.videoCodec), - }, + metadata: { codec: toCodecId(options.videoCodec) }, }; } diff --git a/libraries/scrcpy/src/2_2/impl/index.ts b/libraries/scrcpy/src/2_2/impl/index.ts index 65321195..b06b16a2 100644 --- a/libraries/scrcpy/src/2_2/impl/index.ts +++ b/libraries/scrcpy/src/2_2/impl/index.ts @@ -1,4 +1,4 @@ -export * from "../../2_0/impl/index.js"; +export * from "../../2_1/impl/index.js"; export { Defaults } from "./defaults.js"; export type { Init } from "./init.js"; export { parseDisplay } from "./parse-display.js"; diff --git a/libraries/scrcpy/src/2_2/impl/init.ts b/libraries/scrcpy/src/2_2/impl/init.ts index ee933875..2adb52a6 100644 --- a/libraries/scrcpy/src/2_2/impl/init.ts +++ b/libraries/scrcpy/src/2_2/impl/init.ts @@ -1,8 +1,7 @@ import type { PrevImpl } from "./prev.js"; -export interface Init extends Omit { +export interface Init extends PrevImpl.Init { videoSource?: "display" | "camera"; - displayId?: number; cameraId?: string | undefined; cameraSize?: string | undefined; cameraFacing?: "front" | "back" | "external" | undefined; diff --git a/libraries/scrcpy/src/2_2/impl/prev.ts b/libraries/scrcpy/src/2_2/impl/prev.ts index fb220b06..5628783d 100644 --- a/libraries/scrcpy/src/2_2/impl/prev.ts +++ b/libraries/scrcpy/src/2_2/impl/prev.ts @@ -1 +1 @@ -export * as PrevImpl from "../../2_0/impl/index.js"; +export * as PrevImpl from "../../2_1/impl/index.js"; diff --git a/libraries/scrcpy/src/2_2/options.ts b/libraries/scrcpy/src/2_2/options.ts index 39c36aaa..199ce2f5 100644 --- a/libraries/scrcpy/src/2_2/options.ts +++ b/libraries/scrcpy/src/2_2/options.ts @@ -54,6 +54,10 @@ export class ScrcpyOptions2_2 implements ScrcpyOptions { constructor(init: Init) { this.value = { ...Defaults, ...init }; + if (this.value.videoSource === "camera") { + this.value.control = false; + } + if (this.value.control && this.value.clipboardAutosync) { this.#clipboard = new ClipboardStream(); this.#ackClipboardHandler = new AckClipboardHandler(); diff --git a/libraries/scrcpy/src/2_3/impl/index.ts b/libraries/scrcpy/src/2_3/impl/index.ts index bf044156..21064aca 100644 --- a/libraries/scrcpy/src/2_3/impl/index.ts +++ b/libraries/scrcpy/src/2_3/impl/index.ts @@ -1,2 +1,2 @@ -export * from "../../2_0/impl/index.js"; +export * from "../../2_2/impl/index.js"; export type { Init } from "./init.js"; diff --git a/libraries/scrcpy/src/2_3/impl/prev.ts b/libraries/scrcpy/src/2_3/impl/prev.ts index fb220b06..f88d0d49 100644 --- a/libraries/scrcpy/src/2_3/impl/prev.ts +++ b/libraries/scrcpy/src/2_3/impl/prev.ts @@ -1 +1 @@ -export * as PrevImpl from "../../2_0/impl/index.js"; +export * as PrevImpl from "../../2_2/impl/index.js"; diff --git a/libraries/scrcpy/src/2_3/options.ts b/libraries/scrcpy/src/2_3/options.ts index cd910010..8ab38779 100644 --- a/libraries/scrcpy/src/2_3/options.ts +++ b/libraries/scrcpy/src/2_3/options.ts @@ -54,6 +54,10 @@ export class ScrcpyOptions2_3 implements ScrcpyOptions { constructor(init: Init) { this.value = { ...Defaults, ...init }; + if (this.value.videoSource === "camera") { + this.value.control = false; + } + if (this.value.control && this.value.clipboardAutosync) { this.#clipboard = new ClipboardStream(); this.#ackClipboardHandler = new AckClipboardHandler(); diff --git a/libraries/scrcpy/src/2_4/options.ts b/libraries/scrcpy/src/2_4/options.ts index 0cae9b5e..53ed2719 100644 --- a/libraries/scrcpy/src/2_4/options.ts +++ b/libraries/scrcpy/src/2_4/options.ts @@ -17,6 +17,7 @@ import type { ScrcpyInjectTouchControlMessage, ScrcpySetClipboardControlMessage, ScrcpyUHidCreateControlMessage, + ScrcpyUHidOutputDeviceMessage, } from "../latest.js"; import type { Init } from "./impl/index.js"; @@ -55,13 +56,19 @@ export class ScrcpyOptions2_4 implements ScrcpyOptions { #ackClipboardHandler: AckClipboardHandler | undefined; #uHidOutput: UHidOutputStream | undefined; - get uHidOutput(): UHidOutputStream | undefined { + get uHidOutput(): + | ReadableStream + | undefined { return this.#uHidOutput; } constructor(init: Init) { this.value = { ...Defaults, ...init }; + if (this.value.videoSource === "camera") { + this.value.control = false; + } + if (this.value.control) { if (this.value.clipboardAutosync) { this.#clipboard = new ClipboardStream(); diff --git a/libraries/scrcpy/src/2_6/options.ts b/libraries/scrcpy/src/2_6/options.ts index a6ab9948..dd002860 100644 --- a/libraries/scrcpy/src/2_6/options.ts +++ b/libraries/scrcpy/src/2_6/options.ts @@ -17,9 +17,9 @@ import type { ScrcpyInjectTouchControlMessage, ScrcpySetClipboardControlMessage, ScrcpyUHidCreateControlMessage, + ScrcpyUHidOutputDeviceMessage, } from "../latest.js"; -import type { Init } from "./impl/index.js"; import { AckClipboardHandler, ClipboardStream, @@ -37,7 +37,10 @@ import { serializeUHidCreateControlMessage, setListDisplays, setListEncoders, + UHidOutputStream + } from "./impl/index.js"; +import type {Init} from "./impl/index.js"; export class ScrcpyOptions2_6 implements ScrcpyOptions { readonly value: Required; @@ -53,12 +56,27 @@ export class ScrcpyOptions2_6 implements ScrcpyOptions { #ackClipboardHandler: AckClipboardHandler | undefined; + #uHidOutput: UHidOutputStream | undefined; + get uHidOutput(): + | ReadableStream + | undefined { + return this.#uHidOutput; + } + constructor(init: Init) { this.value = { ...Defaults, ...init }; - if (this.value.control && this.value.clipboardAutosync) { - this.#clipboard = new ClipboardStream(); - this.#ackClipboardHandler = new AckClipboardHandler(); + if (this.value.videoSource === "camera") { + this.value.control = false; + } + + if (this.value.control) { + if (this.value.clipboardAutosync) { + this.#clipboard = new ClipboardStream(); + this.#ackClipboardHandler = new AckClipboardHandler(); + } + + this.#uHidOutput = new UHidOutputStream(); } } diff --git a/libraries/scrcpy/src/2_7/impl/serialize-uhid-create.ts b/libraries/scrcpy/src/2_7/impl/serialize-uhid-create.ts index b3be3ae0..e27be5c0 100644 --- a/libraries/scrcpy/src/2_7/impl/serialize-uhid-create.ts +++ b/libraries/scrcpy/src/2_7/impl/serialize-uhid-create.ts @@ -1,17 +1,19 @@ 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 = struct( - { - type: u8, - id: u16, - name: string(u8), - data: buffer(u16), - }, - { littleEndian: false }, -); +export const UHidCreateControlMessage = /* #__PURE__ */ (() => + struct( + { + type: u8(ScrcpyControlMessageType.UHidCreate), + id: u16, + name: string(u8), + data: buffer(u16), + }, + { littleEndian: false }, + ))(); export type UHidCreateControlMessage = StructInit< typeof UHidCreateControlMessage diff --git a/libraries/scrcpy/src/2_7/options.ts b/libraries/scrcpy/src/2_7/options.ts index 60615707..e4833b92 100644 --- a/libraries/scrcpy/src/2_7/options.ts +++ b/libraries/scrcpy/src/2_7/options.ts @@ -17,6 +17,7 @@ import type { ScrcpyInjectTouchControlMessage, ScrcpySetClipboardControlMessage, ScrcpyUHidCreateControlMessage, + ScrcpyUHidOutputDeviceMessage, } from "../latest.js"; import type { Init } from "./impl/index.js"; @@ -37,6 +38,7 @@ import { serializeUHidCreateControlMessage, setListDisplays, setListEncoders, + UHidOutputStream, } from "./impl/index.js"; export class ScrcpyOptions2_7 implements ScrcpyOptions { @@ -53,12 +55,27 @@ export class ScrcpyOptions2_7 implements ScrcpyOptions { #ackClipboardHandler: AckClipboardHandler | undefined; + #uHidOutput: UHidOutputStream | undefined; + get uHidOutput(): + | ReadableStream + | undefined { + return this.#uHidOutput; + } + constructor(init: Init) { this.value = { ...Defaults, ...init }; - if (this.value.control && this.value.clipboardAutosync) { - this.#clipboard = new ClipboardStream(); - this.#ackClipboardHandler = new AckClipboardHandler(); + if (this.value.videoSource === "camera") { + this.value.control = false; + } + + if (this.value.control) { + if (this.value.clipboardAutosync) { + this.#clipboard = new ClipboardStream(); + this.#ackClipboardHandler = new AckClipboardHandler(); + } + + this.#uHidOutput = new UHidOutputStream(); } } diff --git a/libraries/scrcpy/src/3_0/impl/control-message-types.ts b/libraries/scrcpy/src/3_0/impl/control-message-types.ts index 08f91da8..6252fb79 100644 --- a/libraries/scrcpy/src/3_0/impl/control-message-types.ts +++ b/libraries/scrcpy/src/3_0/impl/control-message-types.ts @@ -2,8 +2,9 @@ import { ScrcpyControlMessageType } from "../../base/index.js"; import { PrevImpl } from "./prev.js"; -export const ControlMessageTypes: readonly ScrcpyControlMessageType[] = [ - ...PrevImpl.ControlMessageTypes, - ScrcpyControlMessageType.StartApp, - ScrcpyControlMessageType.ResetVideo, -]; +export const ControlMessageTypes: readonly ScrcpyControlMessageType[] = + /* #__PURE__ */ (() => [ + ...PrevImpl.ControlMessageTypes, + ScrcpyControlMessageType.StartApp, + ScrcpyControlMessageType.ResetVideo, + ])(); diff --git a/libraries/scrcpy/src/3_0/impl/defaults.ts b/libraries/scrcpy/src/3_0/impl/defaults.ts index acb636c8..76087f20 100644 --- a/libraries/scrcpy/src/3_0/impl/defaults.ts +++ b/libraries/scrcpy/src/3_0/impl/defaults.ts @@ -1,13 +1,16 @@ +import { omit } from "../../utils/index.js"; + import type { Init } from "./init.js"; -import { CaptureOrientation, NewDisplay } from "./init.js"; +import { CaptureOrientation } from "./init.js"; import { PrevImpl } from "./prev.js"; -export const Defaults = { - ...PrevImpl.Defaults, - captureOrientation: CaptureOrientation.Default, - angle: 0, - screenOffTimeout: undefined, - listApps: false, - newDisplay: NewDisplay.Empty, - vdSystemDecorations: true, -} as const satisfies Required; +export const Defaults = /* #__PURE__ */ (() => + ({ + ...omit(PrevImpl.Defaults, "lockVideoOrientation"), + captureOrientation: CaptureOrientation.Unlocked, + angle: 0, + screenOffTimeout: undefined, + listApps: false, + newDisplay: undefined, + vdSystemDecorations: true, + }) as const satisfies Required)(); diff --git a/libraries/scrcpy/src/3_0/impl/index.ts b/libraries/scrcpy/src/3_0/impl/index.ts index 9b163fd2..d67ed902 100644 --- a/libraries/scrcpy/src/3_0/impl/index.ts +++ b/libraries/scrcpy/src/3_0/impl/index.ts @@ -1,5 +1,10 @@ export * from "../../2_7/impl/index.js"; export { ControlMessageTypes } from "./control-message-types.js"; export { Defaults } from "./defaults.js"; -export type { Init } from "./init.js"; -export { EncoderRegex } from "./parse-encoder.js"; +export { + CaptureOrientation, + LockOrientation, + NewDisplay, + Orientation, + type Init, +} from "./init.js"; diff --git a/libraries/scrcpy/src/3_0/impl/init.ts b/libraries/scrcpy/src/3_0/impl/init.ts index b33ada10..a745deeb 100644 --- a/libraries/scrcpy/src/3_0/impl/init.ts +++ b/libraries/scrcpy/src/3_0/impl/init.ts @@ -6,7 +6,7 @@ export const LockOrientation = { Unlocked: 0, LockedInitial: 1, LockedValue: 2, -}; +} as const; export type LockOrientation = (typeof LockOrientation)[keyof typeof LockOrientation]; @@ -16,16 +16,17 @@ export const Orientation = { Orient90: 90, Orient180: 180, Orient270: 270, -}; +} as const; export type Orientation = (typeof Orientation)[keyof typeof Orientation]; export class CaptureOrientation implements ScrcpyOptionValue { - static Default = /* #__PURE__ */ new CaptureOrientation( - LockOrientation.Unlocked, - Orientation.Orient0, - false, - ); + static Unlocked = /* #__PURE__ */ (() => + new CaptureOrientation( + LockOrientation.Unlocked, + Orientation.Orient0, + false, + ))(); lock: LockOrientation; orientation: Orientation; @@ -59,7 +60,7 @@ export class CaptureOrientation implements ScrcpyOptionValue { } export class NewDisplay implements ScrcpyOptionValue { - static Empty = /* #__PURE__ */ new NewDisplay(); + static Default = /* #__PURE__ */ new NewDisplay(); width?: number | undefined; height?: number | undefined; @@ -90,7 +91,7 @@ export class NewDisplay implements ScrcpyOptionValue { this.height === undefined && this.dpi === undefined ) { - return undefined; + return ""; } if (this.width === undefined) { @@ -112,6 +113,9 @@ export interface Init extends Omit { listApps?: boolean; - newDisplay?: NewDisplay; + // `display_id` and `new_display` can't be specified at the same time + // but `serialize` method will exclude options that are same as the default value + // so `displayId: 0` will be ignored + newDisplay?: NewDisplay | undefined; vdSystemDecorations?: boolean; } diff --git a/libraries/scrcpy/src/3_0/impl/parse-encoder.ts b/libraries/scrcpy/src/3_0/impl/parse-encoder.ts deleted file mode 100644 index 19ecda75..00000000 --- a/libraries/scrcpy/src/3_0/impl/parse-encoder.ts +++ /dev/null @@ -1 +0,0 @@ -export const EncoderRegex = /^\s+scrcpy --encoder-name '([^']+)'$/; diff --git a/libraries/scrcpy/src/3_0/index.ts b/libraries/scrcpy/src/3_0/index.ts index dd462652..e1df529f 100644 --- a/libraries/scrcpy/src/3_0/index.ts +++ b/libraries/scrcpy/src/3_0/index.ts @@ -1,2 +1,2 @@ -export * as ScrcpyOptionsX_XXImpl from "./impl/index.js"; +export * as ScrcpyOptions3_0Impl from "./impl/index.js"; export * from "./options.js"; diff --git a/libraries/scrcpy/src/3_0/options.ts b/libraries/scrcpy/src/3_0/options.ts index 59a1d2d6..b717bcd0 100644 --- a/libraries/scrcpy/src/3_0/options.ts +++ b/libraries/scrcpy/src/3_0/options.ts @@ -17,6 +17,7 @@ import type { ScrcpyInjectTouchControlMessage, ScrcpySetClipboardControlMessage, ScrcpyUHidCreateControlMessage, + ScrcpyUHidOutputDeviceMessage, } from "../latest.js"; import type { Init } from "./impl/index.js"; @@ -37,9 +38,10 @@ import { serializeUHidCreateControlMessage, setListDisplays, setListEncoders, + UHidOutputStream, } from "./impl/index.js"; -export class ScrcpyOptionsX_X implements ScrcpyOptions { +export class ScrcpyOptions3_0 implements ScrcpyOptions { readonly value: Required; get controlMessageTypes(): readonly ScrcpyControlMessageType[] { @@ -53,12 +55,27 @@ export class ScrcpyOptionsX_X implements ScrcpyOptions { #ackClipboardHandler: AckClipboardHandler | undefined; + #uHidOutput: UHidOutputStream | undefined; + get uHidOutput(): + | ReadableStream + | undefined { + return this.#uHidOutput; + } + constructor(init: Init) { this.value = { ...Defaults, ...init }; - if (this.value.control && this.value.clipboardAutosync) { - this.#clipboard = new ClipboardStream(); - this.#ackClipboardHandler = new AckClipboardHandler(); + if (this.value.videoSource === "camera") { + this.value.control = false; + } + + if (this.value.control) { + if (this.value.clipboardAutosync) { + this.#clipboard = new ClipboardStream(); + this.#ackClipboardHandler = new AckClipboardHandler(); + } + + this.#uHidOutput = new UHidOutputStream(); } } diff --git a/libraries/scrcpy/src/android/motion-event.ts b/libraries/scrcpy/src/android/motion-event.ts index cff34a13..6f29ed3f 100644 --- a/libraries/scrcpy/src/android/motion-event.ts +++ b/libraries/scrcpy/src/android/motion-event.ts @@ -13,7 +13,7 @@ export const AndroidMotionEventAction = { HoverExit: 10, ButtonPress: 11, ButtonRelease: 12, -}; +} as const; export type AndroidMotionEventAction = (typeof AndroidMotionEventAction)[keyof typeof AndroidMotionEventAction]; @@ -27,7 +27,7 @@ export const AndroidMotionEventButton = { Forward: 16, StylusPrimary: 32, StylusSecondary: 64, -}; +} as const; export type AndroidMotionEventButton = (typeof AndroidMotionEventButton)[keyof typeof AndroidMotionEventButton]; diff --git a/libraries/scrcpy/src/base/control-message-type.ts b/libraries/scrcpy/src/base/control-message-type.ts index 821a4a5d..bf08d918 100644 --- a/libraries/scrcpy/src/base/control-message-type.ts +++ b/libraries/scrcpy/src/base/control-message-type.ts @@ -18,7 +18,7 @@ export const ScrcpyControlMessageType = { OpenHardKeyboardSettings: 15, StartApp: 16, ResetVideo: 17, -}; +} as const; export type ScrcpyControlMessageType = (typeof ScrcpyControlMessageType)[keyof typeof ScrcpyControlMessageType]; diff --git a/libraries/scrcpy/src/base/options.ts b/libraries/scrcpy/src/base/options.ts index af71bc00..8fa00b44 100644 --- a/libraries/scrcpy/src/base/options.ts +++ b/libraries/scrcpy/src/base/options.ts @@ -7,6 +7,7 @@ import type { ScrcpyInjectTouchControlMessage, ScrcpySetClipboardControlMessage, ScrcpyUHidCreateControlMessage, + ScrcpyUHidOutputDeviceMessage, } from "../latest.js"; import type { ScrcpyAudioStreamMetadata } from "./audio.js"; @@ -22,7 +23,11 @@ export interface ScrcpyOptions { value: Required; - get clipboard(): ReadableStream | undefined; + readonly clipboard?: ReadableStream | undefined; + + readonly uHidOutput?: + | ReadableStream + | undefined; serialize(): string[]; diff --git a/libraries/scrcpy/src/base/video.ts b/libraries/scrcpy/src/base/video.ts index 44266644..f0ec8b37 100644 --- a/libraries/scrcpy/src/base/video.ts +++ b/libraries/scrcpy/src/base/video.ts @@ -16,7 +16,17 @@ export const ScrcpyVideoCodecId = { H264: 0x68_32_36_34, H265: 0x68_32_36_35, AV1: 0x00_61_76_31, -}; +} as const; export type ScrcpyVideoCodecId = (typeof ScrcpyVideoCodecId)[keyof typeof ScrcpyVideoCodecId]; + +export const ScrcpyVideoCodecNameMap = /* #__PURE__ */ (() => { + const result = new Map(); + for (const key in ScrcpyVideoCodecId) { + const value = + ScrcpyVideoCodecId[key as keyof typeof ScrcpyVideoCodecId]; + result.set(value, key); + } + return result; +})(); diff --git a/libraries/scrcpy/src/codec/av1.ts b/libraries/scrcpy/src/codec/av1.ts index e8500f64..ce3cc7b1 100644 --- a/libraries/scrcpy/src/codec/av1.ts +++ b/libraries/scrcpy/src/codec/av1.ts @@ -136,7 +136,7 @@ const ObuType = { RedundantFrameHeader: 7, TileList: 8, Padding: 15, -}; +} as const; type ObuType = (typeof ObuType)[keyof typeof ObuType]; @@ -153,7 +153,7 @@ const ColorPrimaries = { Smpte431: 11, Smpte432: 12, Ebu3213: 22, -}; +} as const; const TransferCharacteristics = { Bt709: 1, @@ -173,7 +173,7 @@ const TransferCharacteristics = { Smpte2084: 16, Smpte428: 17, Hlg: 18, -}; +} as const; const MatrixCoefficients = { Identity: 0, @@ -190,7 +190,7 @@ const MatrixCoefficients = { ChromatNcl: 12, ChromatCl: 13, ICtCp: 14, -}; +} as const; export class Av1 extends BitReader { static ObuType = ObuType; @@ -623,13 +623,16 @@ export class Av1 extends BitReader { // const NumPlanes = mono_chrome ? 1 : 3; const color_description_present_flag = !!this.f1(); - let color_primaries = Av1.ColorPrimaries.Unspecified; - let transfer_characteristics = Av1.TransferCharacteristics.Unspecified; - let matrix_coefficients = Av1.MatrixCoefficients.Unspecified; + let color_primaries: Av1.ColorPrimaries = + Av1.ColorPrimaries.Unspecified; + let transfer_characteristics: Av1.TransferCharacteristics = + Av1.TransferCharacteristics.Unspecified; + let matrix_coefficients: Av1.MatrixCoefficients = + Av1.MatrixCoefficients.Unspecified; if (color_description_present_flag) { - color_primaries = this.f(8); - transfer_characteristics = this.f(8); - matrix_coefficients = this.f(8); + color_primaries = this.f(8) as Av1.ColorPrimaries; + transfer_characteristics = this.f(8) as Av1.TransferCharacteristics; + matrix_coefficients = this.f(8) as Av1.MatrixCoefficients; } let color_range = false; diff --git a/libraries/scrcpy/src/control/inject-key-code.ts b/libraries/scrcpy/src/control/inject-key-code.ts index c88af250..0040ed16 100644 --- a/libraries/scrcpy/src/control/inject-key-code.ts +++ b/libraries/scrcpy/src/control/inject-key-code.ts @@ -6,17 +6,19 @@ import type { AndroidKeyEventAction, AndroidKeyEventMeta, } from "../android/index.js"; +import { ScrcpyControlMessageType } from "../base/index.js"; -export const ScrcpyInjectKeyCodeControlMessage = struct( - { - type: u8, - action: u8(), - keyCode: u32(), - repeat: u32, - metaState: u32(), - }, - { littleEndian: false }, -); +export const ScrcpyInjectKeyCodeControlMessage = /* #__PURE__ */ (() => + struct( + { + type: u8(ScrcpyControlMessageType.InjectKeyCode), + action: u8(), + keyCode: u32(), + repeat: u32, + metaState: u32(), + }, + { littleEndian: false }, + ))(); export type ScrcpyInjectKeyCodeControlMessage = StructInit< typeof ScrcpyInjectKeyCodeControlMessage diff --git a/libraries/scrcpy/src/control/message-type-map.ts b/libraries/scrcpy/src/control/message-type-map.ts index d6e19a08..d374b63b 100644 --- a/libraries/scrcpy/src/control/message-type-map.ts +++ b/libraries/scrcpy/src/control/message-type-map.ts @@ -24,7 +24,7 @@ export class ScrcpyControlMessageTypeMap { message: Omit, type: T["type"], ): T { - (message as T).type = this.get(type); + (message as T).type = this.get(type) as ScrcpyControlMessageType; return message as T; } } diff --git a/libraries/scrcpy/src/control/uhid.ts b/libraries/scrcpy/src/control/uhid.ts index 159735d7..a40dc451 100644 --- a/libraries/scrcpy/src/control/uhid.ts +++ b/libraries/scrcpy/src/control/uhid.ts @@ -1,14 +1,17 @@ import type { StructInit } from "@yume-chan/struct"; import { buffer, struct, u16, u8 } from "@yume-chan/struct"; -export const ScrcpyUHidInputControlMessage = struct( - { - type: u8, - id: u16, - data: buffer(u16), - }, - { littleEndian: false }, -); +import { ScrcpyControlMessageType } from "../base/index.js"; + +export const ScrcpyUHidInputControlMessage = /* #__PURE__ */ (() => + struct( + { + type: u8(ScrcpyControlMessageType.UHidInput), + id: u16, + data: buffer(u16), + }, + { littleEndian: false }, + ))(); export type ScrcpyUHidInputControlMessage = StructInit< typeof ScrcpyUHidInputControlMessage diff --git a/libraries/scrcpy/src/control/writer.ts b/libraries/scrcpy/src/control/writer.ts index 1e1c5472..29729a55 100644 --- a/libraries/scrcpy/src/control/writer.ts +++ b/libraries/scrcpy/src/control/writer.ts @@ -10,10 +10,12 @@ import type { ScrcpyInjectScrollControlMessage, ScrcpyInjectTouchControlMessage, ScrcpySetClipboardControlMessage, + ScrcpyUHidCreateControlMessage, } from "../latest.js"; import type { ScrcpyInjectKeyCodeControlMessage } from "./inject-key-code.js"; import { ScrcpyControlMessageSerializer } from "./serializer.js"; +import type { ScrcpyUHidInputControlMessage } from "./uhid.js"; export class ScrcpyControlMessageWriter { #writer: WritableStreamDefaultWriter>; @@ -27,25 +29,23 @@ export class ScrcpyControlMessageWriter { this.#serializer = new ScrcpyControlMessageSerializer(options); } - async write(message: Uint8Array) { - await Consumable.WritableStream.write(this.#writer, message); + write(message: Uint8Array) { + return Consumable.WritableStream.write(this.#writer, message); } - async injectKeyCode( - message: Omit, - ) { - await this.write(this.#serializer.injectKeyCode(message)); + injectKeyCode(message: Omit) { + return this.write(this.#serializer.injectKeyCode(message)); } - async injectText(text: string) { - await this.write(this.#serializer.injectText(text)); + injectText(text: string) { + return this.write(this.#serializer.injectText(text)); } /** * `pressure` is a float value between 0 and 1. */ - async injectTouch(message: Omit) { - await this.write(this.#serializer.injectTouch(message)); + injectTouch(message: Omit) { + return this.write(this.#serializer.injectTouch(message)); } /** @@ -67,24 +67,24 @@ export class ScrcpyControlMessageWriter { } } - async setScreenPowerMode(mode: AndroidScreenPowerMode) { - await this.write(this.#serializer.setDisplayPower(mode)); + setScreenPowerMode(mode: AndroidScreenPowerMode) { + return this.write(this.#serializer.setDisplayPower(mode)); } - async expandNotificationPanel() { - await this.write(this.#serializer.expandNotificationPanel()); + expandNotificationPanel() { + return this.write(this.#serializer.expandNotificationPanel()); } - async expandSettingPanel() { - await this.write(this.#serializer.expandSettingPanel()); + expandSettingPanel() { + return this.write(this.#serializer.expandSettingPanel()); } - async collapseNotificationPanel() { - await this.write(this.#serializer.collapseNotificationPanel()); + collapseNotificationPanel() { + return this.write(this.#serializer.collapseNotificationPanel()); } - async rotateDevice() { - await this.write(this.#serializer.rotateDevice()); + rotateDevice() { + return this.write(this.#serializer.rotateDevice()); } async setClipboard( @@ -99,6 +99,29 @@ export class ScrcpyControlMessageWriter { } } + uHidCreate(message: Omit) { + return this.write(this.#serializer.uHidCreate(message)); + } + + uHidInput(message: Omit) { + return this.write(this.#serializer.uHidInput(message)); + } + + uHidDestroy(id: number) { + return this.write(this.#serializer.uHidDestroy(id)); + } + + startApp( + name: string, + options?: { forceStop?: boolean; searchByName?: boolean }, + ) { + return this.write(this.#serializer.startApp(name, options)); + } + + resetVideo() { + return this.write(this.#serializer.resetVideo()); + } + releaseLock() { this.#writer.releaseLock(); } diff --git a/libraries/scrcpy/src/index.ts b/libraries/scrcpy/src/index.ts index f75835c9..3f4a990d 100644 --- a/libraries/scrcpy/src/index.ts +++ b/libraries/scrcpy/src/index.ts @@ -17,6 +17,7 @@ export * from "./2_4/index.js"; export * from "./2_5.js"; export * from "./2_6/index.js"; export * from "./2_7/index.js"; +export * from "./3_0/index.js"; export * from "./android/index.js"; export * from "./base/index.js"; export * from "./codec/index.js"; diff --git a/libraries/scrcpy/src/latest.ts b/libraries/scrcpy/src/latest.ts index 3dc81324..a77548f7 100644 --- a/libraries/scrcpy/src/latest.ts +++ b/libraries/scrcpy/src/latest.ts @@ -1,8 +1,15 @@ export { BackOrScreenOnControlMessage as ScrcpyBackOrScreenOnControlMessage, + CaptureOrientation as ScrcpyCaptureOrientation, + CodecOptions as ScrcpyCodecOptions, InjectScrollControlMessage as ScrcpyInjectScrollControlMessage, InjectTouchControlMessage as ScrcpyInjectTouchControlMessage, InstanceId as ScrcpyInstanceId, + LockOrientation as ScrcpyLockOrientation, + NewDisplay as ScrcpyNewDisplay, + Orientation as ScrcpyOrientation, + PointerId as ScrcpyPointerId, SetClipboardControlMessage as ScrcpySetClipboardControlMessage, UHidCreateControlMessage as ScrcpyUHidCreateControlMessage, -} from "./2_7/impl/index.js"; + UHidOutputDeviceMessage as ScrcpyUHidOutputDeviceMessage, +} from "./3_0/impl/index.js"; diff --git a/libraries/scrcpy/src/utils/clamp.ts b/libraries/scrcpy/src/utils/clamp.ts new file mode 100644 index 00000000..300d79dc --- /dev/null +++ b/libraries/scrcpy/src/utils/clamp.ts @@ -0,0 +1,11 @@ +export function clamp(value: number, min: number, max: number): number { + if (value < min) { + return min; + } + + if (value > max) { + return max; + } + + return value; +} diff --git a/libraries/scrcpy/src/utils/index.ts b/libraries/scrcpy/src/utils/index.ts index 90ebc3c5..9473ae27 100644 --- a/libraries/scrcpy/src/utils/index.ts +++ b/libraries/scrcpy/src/utils/index.ts @@ -1,2 +1,4 @@ +export * from "./clamp.js"; export * from "./constants.js"; +export * from "./omit.js"; export * from "./wrapper.js"; diff --git a/libraries/scrcpy/src/utils/omit.ts b/libraries/scrcpy/src/utils/omit.ts new file mode 100644 index 00000000..08fd47a8 --- /dev/null +++ b/libraries/scrcpy/src/utils/omit.ts @@ -0,0 +1,11 @@ +/* #__NO_SIDE_EFFECTS__ */ +export function omit, K extends (keyof T)[]>( + value: T, + ...keys: K +): T { + return Object.fromEntries( + Object.entries(value).filter( + ([key]) => !keys.includes(key as K[number]), + ), + ) as never; +} diff --git a/libraries/scrcpy/src/utils/wrapper.ts b/libraries/scrcpy/src/utils/wrapper.ts index c31519d5..99b43c2c 100644 --- a/libraries/scrcpy/src/utils/wrapper.ts +++ b/libraries/scrcpy/src/utils/wrapper.ts @@ -32,6 +32,10 @@ export class ScrcpyOptionsWrapper return this.#base.clipboard; } + get uHidOutput() { + return this.#base.uHidOutput; + } + constructor(options: ScrcpyOptions) { this.#base = options; } diff --git a/libraries/stream-extra/src/stream.ts b/libraries/stream-extra/src/stream.ts index e5591609..778d1aed 100644 --- a/libraries/stream-extra/src/stream.ts +++ b/libraries/stream-extra/src/stream.ts @@ -1,11 +1,13 @@ import type { AbortSignal, + ReadableStreamIteratorOptions, ReadableStream as ReadableStreamType, TransformStream as TransformStreamType, WritableStream as WritableStreamType, } from "./types.js"; export * from "./types.js"; +export { ReadableStream }; /** A controller object that allows you to abort one or more DOM requests as and when desired. */ export interface AbortController { @@ -32,13 +34,70 @@ interface GlobalExtension { TransformStream: typeof TransformStreamType; } +export const { AbortController } = globalThis as unknown as GlobalExtension; + export type ReadableStream = ReadableStreamType; export type WritableStream = WritableStreamType; export type TransformStream = TransformStreamType; -export const { - AbortController, - ReadableStream, - WritableStream, - TransformStream, -} = globalThis as unknown as GlobalExtension; +const ReadableStream = /* #__PURE__ */ (() => { + const { ReadableStream } = globalThis as unknown as GlobalExtension; + + if (!ReadableStream.from) { + ReadableStream.from = function (iterable) { + const iterator = + Symbol.asyncIterator in iterable + ? iterable[Symbol.asyncIterator]() + : iterable[Symbol.iterator](); + + return new ReadableStream({ + async pull(controller) { + const result = await iterator.next(); + if (result.done) { + controller.close(); + return; + } + controller.enqueue(result.value); + }, + async cancel(reason) { + await iterator.return?.(reason); + }, + }); + }; + } + + if ( + !ReadableStream.prototype[Symbol.asyncIterator] || + !ReadableStream.prototype.values + ) { + ReadableStream.prototype.values = async function* ( + this: ReadableStream, + options?: ReadableStreamIteratorOptions, + ) { + const reader = this.getReader(); + try { + while (true) { + const { done, value } = await reader.read(); + if (done) { + return; + } + yield value; + } + } finally { + if (!options?.preventCancel) { + await reader.cancel(); + } + reader.releaseLock(); + } + }; + + ReadableStream.prototype[Symbol.asyncIterator] = + // eslint-disable-next-line @typescript-eslint/unbound-method + ReadableStream.prototype.values; + } + + return ReadableStream; +})(); + +export const { WritableStream, TransformStream } = + globalThis as unknown as GlobalExtension; diff --git a/libraries/stream-extra/src/types.ts b/libraries/stream-extra/src/types.ts index 92c008c6..7175eb83 100644 --- a/libraries/stream-extra/src/types.ts +++ b/libraries/stream-extra/src/types.ts @@ -242,7 +242,7 @@ export declare class ReadableStream implements AsyncIterable { * such as an array, an async generator, or a Node.js readable stream. */ static from( - asyncIterable: Iterable | AsyncIterable | ReadableStreamLike, + asyncIterable: Iterable | AsyncIterable, ): ReadableStream; } @@ -253,7 +253,7 @@ export declare class ReadableStream implements AsyncIterable { */ export declare interface ReadableStreamAsyncIterator extends AsyncIterableIterator { - next(): Promise>; + next(): Promise>; return(value?: R): Promise>; } diff --git a/libraries/struct/src/buffer.ts b/libraries/struct/src/buffer.ts index 858a5876..683c1074 100644 --- a/libraries/struct/src/buffer.ts +++ b/libraries/struct/src/buffer.ts @@ -42,9 +42,7 @@ export interface BufferLike { export const EmptyUint8Array = new Uint8Array(0); -// Rollup doesn't support `/* #__NO_SIDE_EFFECTS__ */ export const a = () => {} -/* #__NO_SIDE_EFFECTS__ */ -function _buffer( +export const buffer: BufferLike = function ( lengthOrField: | string | number @@ -259,6 +257,4 @@ function _buffer( return reader.readExactly(length); }, }; -} - -export const buffer: BufferLike = _buffer as never; +} as never; diff --git a/libraries/struct/src/number.ts b/libraries/struct/src/number.ts index df430ba1..4f8df5ad 100644 --- a/libraries/struct/src/number.ts +++ b/libraries/struct/src/number.ts @@ -18,11 +18,11 @@ import { bipedal } from "./bipedal.js"; import type { Field } from "./field.js"; export interface NumberField extends Field { - (infer?: T): Field; + (infer?: U): Field; } /* #__NO_SIDE_EFFECTS__ */ -function number( +function factory( size: number, serialize: Field["serialize"], deserialize: Field["deserialize"], @@ -34,7 +34,7 @@ function number( return result as never; } -export const u8 = number( +export const u8 = factory( 1, (value, { buffer, index }) => { buffer[index] = value; @@ -45,7 +45,7 @@ export const u8 = number( }), ); -export const s8 = number( +export const s8 = factory( 1, (value, { buffer, index }) => { buffer[index] = value; @@ -56,7 +56,7 @@ export const s8 = number( }), ); -export const u16 = number( +export const u16 = factory( 2, (value, { buffer, index, littleEndian }) => { setUint16(buffer, index, value, littleEndian); @@ -67,7 +67,7 @@ export const u16 = number( }), ); -export const s16 = number( +export const s16 = factory( 2, (value, { buffer, index, littleEndian }) => { setInt16(buffer, index, value, littleEndian); @@ -78,7 +78,7 @@ export const s16 = number( }), ); -export const u32 = number( +export const u32 = factory( 4, (value, { buffer, index, littleEndian }) => { setUint32(buffer, index, value, littleEndian); @@ -89,7 +89,7 @@ export const u32 = number( }), ); -export const s32 = number( +export const s32 = factory( 4, (value, { buffer, index, littleEndian }) => { setInt32(buffer, index, value, littleEndian); @@ -100,7 +100,7 @@ export const s32 = number( }), ); -export const u64 = number( +export const u64 = factory( 8, (value, { buffer, index, littleEndian }) => { setUint64(buffer, index, value, littleEndian); @@ -111,7 +111,7 @@ export const u64 = number( }), ); -export const s64 = number( +export const s64 = factory( 8, (value, { buffer, index, littleEndian }) => { setInt64(buffer, index, value, littleEndian); diff --git a/libraries/struct/src/utils.ts b/libraries/struct/src/utils.ts index 06a3613a..a2aa4166 100644 --- a/libraries/struct/src/utils.ts +++ b/libraries/struct/src/utils.ts @@ -33,6 +33,7 @@ export function encodeUtf8(input: string): Uint8Array { return SharedEncoder.encode(input); } +/* #__NO_SIDE_EFFECTS__ */ export function decodeUtf8(buffer: ArrayBufferView | ArrayBuffer): string { // `TextDecoder` has internal states in stream mode, // but this method is not for stream mode, so the instance can be reused diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8e1700fc..6dcf88f4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -439,21 +439,6 @@ importers: specifier: ^5.6.3 version: 5.6.3 - libraries/scrcpy/side-effect-test: - devDependencies: - '@rollup/plugin-node-resolve': - specifier: ^15.3.0 - version: 15.3.0(rollup@4.27.4) - '@rollup/plugin-terser': - specifier: ^0.4.4 - version: 0.4.4(rollup@4.27.4) - '@rollup/plugin-typescript': - specifier: ^12.1.1 - version: 12.1.1(rollup@4.27.4)(tslib@2.8.1)(typescript@5.6.3) - rollup: - specifier: ^4.27.4 - version: 4.27.4 - libraries/stream-extra: dependencies: '@yume-chan/async': @@ -544,6 +529,31 @@ importers: specifier: ^2.2.3 version: 2.2.3 + toolchain/side-effect-test: + dependencies: + '@yume-chan/adb': + specifier: workspace:^ + version: link:../../libraries/adb + '@yume-chan/struct': + specifier: workspace:^ + version: link:../../libraries/struct + devDependencies: + '@rollup/plugin-node-resolve': + specifier: ^15.3.0 + version: 15.3.0(rollup@4.27.4) + '@rollup/plugin-terser': + specifier: ^0.4.4 + version: 0.4.4(rollup@4.27.4) + '@rollup/plugin-typescript': + specifier: ^12.1.1 + version: 12.1.1(rollup@4.27.4)(tslib@2.8.1)(typescript@5.6.3) + rollup: + specifier: ^4.27.4 + version: 4.27.4 + tslib: + specifier: ^2.8.1 + version: 2.8.1 + toolchain/test-runner: devDependencies: '@types/node': diff --git a/libraries/scrcpy/side-effect-test/package.json b/toolchain/side-effect-test/package.json similarity index 75% rename from libraries/scrcpy/side-effect-test/package.json rename to toolchain/side-effect-test/package.json index f0d9bfc1..3882f0a1 100644 --- a/libraries/scrcpy/side-effect-test/package.json +++ b/toolchain/side-effect-test/package.json @@ -14,6 +14,11 @@ "@rollup/plugin-node-resolve": "^15.3.0", "@rollup/plugin-terser": "^0.4.4", "@rollup/plugin-typescript": "^12.1.1", - "rollup": "^4.27.4" + "rollup": "^4.27.4", + "tslib": "^2.8.1" + }, + "dependencies": { + "@yume-chan/adb": "workspace:^", + "@yume-chan/struct": "workspace:^" } } diff --git a/libraries/scrcpy/side-effect-test/rollup.config.ts b/toolchain/side-effect-test/rollup.config.ts similarity index 100% rename from libraries/scrcpy/side-effect-test/rollup.config.ts rename to toolchain/side-effect-test/rollup.config.ts diff --git a/toolchain/side-effect-test/src/index.js b/toolchain/side-effect-test/src/index.js new file mode 100644 index 00000000..05231919 --- /dev/null +++ b/toolchain/side-effect-test/src/index.js @@ -0,0 +1,33 @@ +import { + bipedal, + buffer, + decodeUtf8, + encodeUtf8, + s16, + s32, + s64, + s8, + string, + struct, + u16, + u32, + u64, + u8, +} from "@yume-chan/struct"; + +bipedal(function () {}); +buffer(u8); +decodeUtf8(new Uint8Array()); +encodeUtf8(""); +s16(1); +s32(1); +s64(1); +s8(1); +string(1); +u16(1); +u32(1); +u64(1); +u8(1); +struct({}, {}); + +export * from "@yume-chan/struct";