refactor(scrcpy): rewrite option classes to improve tree-shaking

This commit is contained in:
Simon Chan 2024-11-27 14:43:33 +08:00
parent 92472007db
commit cc5d52912e
No known key found for this signature in database
GPG key ID: A8B69F750B9BCEDD
63 changed files with 595 additions and 325 deletions

View file

@ -46,6 +46,8 @@ export class AdbScrcpyOptions1_16 extends AdbScrcpyOptions<
const client = await AdbScrcpyClient.start(adb, path, version, options); const client = await AdbScrcpyClient.start(adb, path, version, options);
const encoders: ScrcpyEncoder[] = []; const encoders: ScrcpyEncoder[] = [];
// `client.stdout` is supplied by user and may not support async iteration
await client.stdout.pipeTo( await client.stdout.pipeTo(
new WritableStream({ new WritableStream({
write: (line) => { write: (line) => {

View file

@ -2,7 +2,6 @@ import type { MaybeConsumable, WritableStream } from "@yume-chan/stream-extra";
import { ReadableStream } from "@yume-chan/stream-extra"; import { ReadableStream } from "@yume-chan/stream-extra";
import type { Adb, AdbSocket } from "../../../adb.js"; import type { Adb, AdbSocket } from "../../../adb.js";
import { unreachable } from "../../../utils/index.js";
import type { AdbSubprocessProtocol } from "./types.js"; import type { AdbSubprocessProtocol } from "./types.js";
@ -64,10 +63,9 @@ export class AdbSubprocessNoneProtocol implements AdbSubprocessProtocol {
this.#socket = socket; this.#socket = socket;
this.#stderr = new ReadableStream({ this.#stderr = new ReadableStream({
start: (controller) => { start: async (controller) => {
this.#socket.closed await this.#socket.closed;
.then(() => controller.close()) controller.close();
.catch(unreachable);
}, },
}); });
this.#exit = socket.closed.then(() => 0); this.#exit = socket.closed.then(() => 0);

View file

@ -15,23 +15,25 @@ export interface AdbSyncEntry extends AdbSyncStat {
name: string; name: string;
} }
export const AdbSyncEntryResponse = struct( export const AdbSyncEntryResponse = /* #__PURE__ */ (() =>
/* #__PURE__ */ (() => ({ struct(
{
...AdbSyncLstatResponse.fields, ...AdbSyncLstatResponse.fields,
name: string(u32), name: string(u32),
}))(), },
{ littleEndian: true, extra: AdbSyncLstatResponse.extra }, { littleEndian: true, extra: AdbSyncLstatResponse.extra },
); ))();
export type AdbSyncEntryResponse = StructValue<typeof AdbSyncEntryResponse>; export type AdbSyncEntryResponse = StructValue<typeof AdbSyncEntryResponse>;
export const AdbSyncEntry2Response = struct( export const AdbSyncEntry2Response = /* #__PURE__ */ (() =>
/* #__PURE__ */ (() => ({ struct(
{
...AdbSyncStatResponse.fields, ...AdbSyncStatResponse.fields,
name: string(u32), name: string(u32),
}))(), },
{ littleEndian: true, extra: AdbSyncStatResponse.extra }, { littleEndian: true, extra: AdbSyncStatResponse.extra },
); ))();
export type AdbSyncEntry2Response = StructValue<typeof AdbSyncEntry2Response>; export type AdbSyncEntry2Response = StructValue<typeof AdbSyncEntry2Response>;

View file

@ -43,11 +43,13 @@ export interface AdbCredentialStore {
iterateKeys(): AdbKeyIterable; iterateKeys(): AdbKeyIterable;
} }
export enum AdbAuthType { export const AdbAuthType = {
Token = 1, Token: 1,
Signature = 2, Signature: 2,
PublicKey = 3, PublicKey: 3,
} } as const;
export type AdbAuthType = (typeof AdbAuthType)[keyof typeof AdbAuthType];
export interface AdbAuthenticator { export interface AdbAuthenticator {
/** /**

View file

@ -28,7 +28,8 @@ import { AdbCommand, calculateChecksum } from "./packet.js";
export const ADB_DAEMON_VERSION_OMIT_CHECKSUM = 0x01000001; export const ADB_DAEMON_VERSION_OMIT_CHECKSUM = 0x01000001;
// https://android.googlesource.com/platform/packages/modules/adb/+/79010dc6d5ca7490c493df800d4421730f5466ca/transport.cpp#1252 // 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). // 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 = [ export const ADB_DAEMON_DEFAULT_FEATURES = /* #__PURE__ */ (() =>
[
AdbFeature.ShellV2, AdbFeature.ShellV2,
AdbFeature.Cmd, AdbFeature.Cmd,
AdbFeature.StatV2, AdbFeature.StatV2,
@ -48,7 +49,7 @@ export const ADB_DAEMON_DEFAULT_FEATURES = [
"sendrecv_v2_zstd", "sendrecv_v2_zstd",
"sendrecv_v2_dry_run_send", "sendrecv_v2_dry_run_send",
AdbFeature.DelayedAck, AdbFeature.DelayedAck,
] as AdbFeature[]; ] as AdbFeature[])();
export const ADB_DAEMON_DEFAULT_INITIAL_PAYLOAD_SIZE = 32 * 1024 * 1024; export const ADB_DAEMON_DEFAULT_INITIAL_PAYLOAD_SIZE = 32 * 1024 * 1024;
export type AdbDaemonConnection = ReadableWritablePair< export type AdbDaemonConnection = ReadableWritablePair<

View file

@ -211,12 +211,10 @@ export class BugReport extends AdbCommandBase {
let filename: string | undefined; let filename: string | undefined;
let error: string | undefined; let error: string | undefined;
await process.stdout for await (const line of process.stdout
.pipeThrough(new TextDecoderStream()) .pipeThrough(new TextDecoderStream())
.pipeThrough(new SplitStringStream("\n")) // Each chunk should contain one or several full lines
.pipeTo( .pipeThrough(new SplitStringStream("\n"))) {
new WritableStream<string>({
write(line) {
// `BEGIN:` and `PROGRESS:` only appear when `-p` is specified. // `BEGIN:` and `PROGRESS:` only appear when `-p` is specified.
let match = line.match(BugReport.PROGRESS_REGEX); let match = line.match(BugReport.PROGRESS_REGEX);
if (match) { if (match) {
@ -239,9 +237,7 @@ export class BugReport extends AdbCommandBase {
// We want to gather all output. // We want to gather all output.
error = match[1]; error = match[1];
} }
}, }
}),
);
if (error) { if (error) {
throw new Error(error); throw new Error(error);

View file

@ -8,7 +8,6 @@ import {
SplitStringStream, SplitStringStream,
TextDecoderStream, TextDecoderStream,
WrapReadableStream, WrapReadableStream,
WritableStream,
} from "@yume-chan/stream-extra"; } from "@yume-chan/stream-extra";
import type { AsyncExactReadable, StructValue } from "@yume-chan/struct"; import type { AsyncExactReadable, StructValue } from "@yume-chan/struct";
import { decodeUtf8, struct, u16, u32 } from "@yume-chan/struct"; import { decodeUtf8, struct, u16, u32 } from "@yume-chan/struct";
@ -437,13 +436,10 @@ export class Logcat extends AdbCommandBase {
]); ]);
const result: LogSize[] = []; const result: LogSize[] = [];
await stdout for await (const line of stdout
.pipeThrough(new TextDecoderStream()) .pipeThrough(new TextDecoderStream())
.pipeThrough(new SplitStringStream("\n")) .pipeThrough(new SplitStringStream("\n"))) {
.pipeTo( let match = line.match(Logcat.LOG_SIZE_REGEX_11);
new WritableStream({
write(chunk) {
let match = chunk.match(Logcat.LOG_SIZE_REGEX_11);
if (match) { if (match) {
result.push({ result.push({
id: Logcat.logNameToId(match[1]!), id: Logcat.logNameToId(match[1]!),
@ -462,10 +458,10 @@ export class Logcat extends AdbCommandBase {
maxEntrySize: parseInt(match[8]!, 10), maxEntrySize: parseInt(match[8]!, 10),
maxPayloadSize: parseInt(match[9]!, 10), maxPayloadSize: parseInt(match[9]!, 10),
}); });
return; break;
} }
match = chunk.match(Logcat.LOG_SIZE_REGEX_10); match = line.match(Logcat.LOG_SIZE_REGEX_10);
if (match) { if (match) {
result.push({ result.push({
id: Logcat.logNameToId(match[1]!), id: Logcat.logNameToId(match[1]!),
@ -481,9 +477,7 @@ export class Logcat extends AdbCommandBase {
maxPayloadSize: parseInt(match[7]!, 10), maxPayloadSize: parseInt(match[7]!, 10),
}); });
} }
}, }
}),
);
return result; return result;
} }

View file

@ -1 +0,0 @@
export * from "../../esm/index";

View file

@ -6,7 +6,7 @@ export const VideoOrientation = {
Landscape: 1, Landscape: 1,
PortraitFlipped: 2, PortraitFlipped: 2,
LandscapeFlipped: 3, LandscapeFlipped: 3,
}; } as const;
export type VideoOrientation = export type VideoOrientation =
(typeof VideoOrientation)[keyof typeof 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 { export interface Init {
logLevel?: LogLevel; logLevel?: LogLevel;

View file

@ -4,18 +4,7 @@ import { bipedal, struct, u16, u32, u64, u8 } from "@yume-chan/struct";
import type { AndroidMotionEventAction } from "../../android/index.js"; import type { AndroidMotionEventAction } from "../../android/index.js";
import type { ScrcpyInjectTouchControlMessage } from "../../latest.js"; import type { ScrcpyInjectTouchControlMessage } from "../../latest.js";
import { clamp } from "../../utils/index.js";
export function clamp(value: number, min: number, max: number): number {
if (value < min) {
return min;
}
if (value > max) {
return max;
}
return value;
}
export const UnsignedFloat: Field<number, never, never> = { export const UnsignedFloat: Field<number, never, never> = {
size: 2, size: 2,
@ -33,6 +22,13 @@ export const UnsignedFloat: Field<number, never, never> = {
}), }),
}; };
export const PointerId = {
Mouse: -1n,
Finger: -2n,
VirtualMouse: -3n,
VirtualFinger: -4n,
} as const;
export const InjectTouchControlMessage = struct( export const InjectTouchControlMessage = struct(
{ {
type: u8, type: u8,

View file

@ -5,6 +5,7 @@ export {
} from "./back-or-screen-on.js"; } from "./back-or-screen-on.js";
export { ControlMessageTypes } from "./control-message-types.js"; export { ControlMessageTypes } from "./control-message-types.js";
export { Defaults } from "./defaults.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 { EncoderRegex } from "./parse-encoder.js";
export { SerializeOrder } from "./serialize-order.js"; export { SerializeOrder } from "./serialize-order.js";

View file

@ -9,7 +9,7 @@ export const VideoOrientation = {
Landscape: 1, Landscape: 1,
PortraitFlipped: 2, PortraitFlipped: 2,
LandscapeFlipped: 3, LandscapeFlipped: 3,
}; } as const;
export type VideoOrientation = export type VideoOrientation =
(typeof VideoOrientation)[keyof typeof VideoOrientation]; (typeof VideoOrientation)[keyof typeof VideoOrientation];

View file

@ -4,15 +4,16 @@ function toSnakeCase(input: string): string {
return input.replace(/([A-Z])/g, "_$1").toLowerCase(); return input.replace(/([A-Z])/g, "_$1").toLowerCase();
} }
// 1.21 changed the format of arguments
export function serialize<T extends object>( export function serialize<T extends object>(
options: T, options: T,
defaults: Required<T>, defaults: Required<T>,
): string[] { ): string[] {
// 1.21 changed the format of arguments
const result: string[] = []; const result: string[] = [];
for (const [key, value] of Object.entries(options)) { for (const [key, value] of Object.entries(options)) {
const serializedValue = toScrcpyOptionValue(value, undefined); const serializedValue = toScrcpyOptionValue(value, undefined);
if (!serializedValue) { // v3.0 `new_display` option needs to send empty strings to server
if (serializedValue === undefined) {
continue; continue;
} }
@ -20,7 +21,7 @@ export function serialize<T extends object>(
defaults[key as keyof T], defaults[key as keyof T],
undefined, undefined,
); );
if (serializedValue == defaultValue) { if (serializedValue === defaultValue) {
continue; continue;
} }

View file

@ -1,6 +1,8 @@
import type { StructInit } from "@yume-chan/struct"; import type { StructInit } from "@yume-chan/struct";
import { s32, struct } from "@yume-chan/struct"; import { s32, struct } from "@yume-chan/struct";
import type { ScrcpyInjectScrollControlMessage } from "../../latest.js";
import { PrevImpl } from "./prev.js"; import { PrevImpl } from "./prev.js";
export const InjectScrollControlMessage = /* #__PURE__ */ (() => export const InjectScrollControlMessage = /* #__PURE__ */ (() =>
@ -18,7 +20,7 @@ export type InjectScrollControlMessage = StructInit<
export class ScrollController extends PrevImpl.ScrollController { export class ScrollController extends PrevImpl.ScrollController {
override serializeScrollMessage( override serializeScrollMessage(
message: InjectScrollControlMessage, message: ScrcpyInjectScrollControlMessage,
): Uint8Array | undefined { ): Uint8Array | undefined {
const processed = this.processMessage(message); const processed = this.processMessage(message);
if (!processed) { if (!processed) {

View file

@ -2,16 +2,16 @@ import { getInt16, setInt16 } from "@yume-chan/no-data-view";
import type { Field, StructInit } from "@yume-chan/struct"; import type { Field, StructInit } from "@yume-chan/struct";
import { bipedal, struct, u16, u32, u8 } 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 { ScrcpyScrollController } from "../../base/index.js";
import type { ScrcpyInjectScrollControlMessage } from "../../latest.js"; import type { ScrcpyInjectScrollControlMessage } from "../../latest.js";
import { clamp } from "../../utils/index.js";
import { PrevImpl } from "./prev.js";
export const SignedFloat: Field<number, never, never> = { export const SignedFloat: Field<number, never, never> = {
size: 2, size: 2,
serialize(value, { buffer, index, littleEndian }) { serialize(value, { buffer, index, littleEndian }) {
// https://github.com/Genymobile/scrcpy/blob/1f138aef41de651668043b32c4effc2d4adbfc44/app/src/util/binary.h#L51 // 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; value = value === 1 ? 0x7fff : value * 0x8000;
setInt16(buffer, index, value, littleEndian); setInt16(buffer, index, value, littleEndian);
}, },
@ -23,9 +23,10 @@ export const SignedFloat: Field<number, never, never> = {
}), }),
}; };
export const InjectScrollControlMessage = struct( export const InjectScrollControlMessage = /* #__PURE__ */ (() =>
struct(
{ {
type: u8, type: u8(ScrcpyControlMessageType.InjectScroll),
pointerX: u32, pointerX: u32,
pointerY: u32, pointerY: u32,
screenWidth: u16, screenWidth: u16,
@ -35,7 +36,7 @@ export const InjectScrollControlMessage = struct(
buttons: u32, buttons: u32,
}, },
{ littleEndian: false }, { littleEndian: false },
); ))();
export type InjectScrollControlMessage = StructInit< export type InjectScrollControlMessage = StructInit<
typeof InjectScrollControlMessage typeof InjectScrollControlMessage

View file

@ -1,10 +1,12 @@
import { omit } from "../../utils/index.js";
import type { Init } from "./init.js"; import type { Init } from "./init.js";
import { InstanceId } from "./init.js"; import { InstanceId } from "./init.js";
import { PrevImpl } from "./prev.js"; import { PrevImpl } from "./prev.js";
export const Defaults = /* #__PURE__ */ (() => export const Defaults = /* #__PURE__ */ (() =>
({ ({
...PrevImpl.Defaults, ...omit(PrevImpl.Defaults, "bitRate", "codecOptions", "encoderName"),
scid: InstanceId.NONE, scid: InstanceId.NONE,
videoCodec: "h264", videoCodec: "h264",

View file

@ -6,6 +6,7 @@ export {
serializeInjectTouchControlMessage, serializeInjectTouchControlMessage,
} from "./inject-touch.js"; } from "./inject-touch.js";
export * from "./parse-audio-stream-metadata.js"; export * from "./parse-audio-stream-metadata.js";
export { parseDisplay } from "./parse-display.js";
export { parseEncoder } from "./parse-encoder.js"; export { parseEncoder } from "./parse-encoder.js";
export { parseVideoStreamMetadata } from "./parse-video-stream-metadata.js"; export { parseVideoStreamMetadata } from "./parse-video-stream-metadata.js";
export { setListDisplays } from "./set-list-display.js"; export { setListDisplays } from "./set-list-display.js";

View file

@ -2,13 +2,15 @@ import type { StructInit } from "@yume-chan/struct";
import { struct, u16, u32, u64, u8 } from "@yume-chan/struct"; import { struct, u16, u32, u64, u8 } from "@yume-chan/struct";
import type { AndroidMotionEventAction } from "../../android/motion-event.js"; import type { AndroidMotionEventAction } from "../../android/motion-event.js";
import { ScrcpyControlMessageType } from "../../base/control-message-type.js";
import type { ScrcpyInjectTouchControlMessage } from "../../latest.js"; import type { ScrcpyInjectTouchControlMessage } from "../../latest.js";
import { PrevImpl } from "./prev.js"; import { PrevImpl } from "./prev.js";
export const InjectTouchControlMessage = struct( export const InjectTouchControlMessage = /* #__PURE__ */ (() =>
struct(
{ {
type: u8, type: u8(ScrcpyControlMessageType.InjectTouch),
action: u8<AndroidMotionEventAction>(), action: u8<AndroidMotionEventAction>(),
pointerId: u64, pointerId: u64,
pointerX: u32, pointerX: u32,
@ -20,7 +22,7 @@ export const InjectTouchControlMessage = struct(
buttons: u32, buttons: u32,
}, },
{ littleEndian: false }, { littleEndian: false },
); ))();
export type InjectTouchControlMessage = StructInit< export type InjectTouchControlMessage = StructInit<
typeof InjectTouchControlMessage typeof InjectTouchControlMessage

View file

@ -1,6 +1,8 @@
import type { ScrcpyDisplay } from "../../base/index.js"; import type { ScrcpyDisplay } from "../../base/index.js";
export function parseDisplay(line: string): ScrcpyDisplay | undefined { 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+\(([^)]+)\)$/); const match = line.match(/^\s+--display=(\d+)\s+\(([^)]+)\)$/);
if (match) { if (match) {
const display: ScrcpyDisplay = { const display: ScrcpyDisplay = {

View file

@ -41,7 +41,7 @@ async function parseAsync(
let width: number | undefined; let width: number | undefined;
let height: number | undefined; let height: number | undefined;
if (options.sendCodecMeta) { if (options.sendCodecMeta) {
codec = await PrevImpl.readU32(buffered); codec = (await PrevImpl.readU32(buffered)) as ScrcpyVideoCodecId;
width = await PrevImpl.readU32(buffered); width = await PrevImpl.readU32(buffered);
height = await PrevImpl.readU32(buffered); height = await PrevImpl.readU32(buffered);
} else { } else {
@ -64,9 +64,7 @@ export function parseVideoStreamMetadata(
if (!options.sendDeviceMeta && !options.sendCodecMeta) { if (!options.sendDeviceMeta && !options.sendCodecMeta) {
return { return {
stream, stream,
metadata: { metadata: { codec: toCodecId(options.videoCodec) },
codec: toCodecId(options.videoCodec),
},
}; };
} }

View file

@ -1,4 +1,4 @@
export * from "../../2_0/impl/index.js"; export * from "../../2_1/impl/index.js";
export { Defaults } from "./defaults.js"; export { Defaults } from "./defaults.js";
export type { Init } from "./init.js"; export type { Init } from "./init.js";
export { parseDisplay } from "./parse-display.js"; export { parseDisplay } from "./parse-display.js";

View file

@ -1,8 +1,7 @@
import type { PrevImpl } from "./prev.js"; import type { PrevImpl } from "./prev.js";
export interface Init extends Omit<PrevImpl.Init, "display"> { export interface Init extends PrevImpl.Init {
videoSource?: "display" | "camera"; videoSource?: "display" | "camera";
displayId?: number;
cameraId?: string | undefined; cameraId?: string | undefined;
cameraSize?: string | undefined; cameraSize?: string | undefined;
cameraFacing?: "front" | "back" | "external" | undefined; cameraFacing?: "front" | "back" | "external" | undefined;

View file

@ -1 +1 @@
export * as PrevImpl from "../../2_0/impl/index.js"; export * as PrevImpl from "../../2_1/impl/index.js";

View file

@ -54,6 +54,10 @@ export class ScrcpyOptions2_2 implements ScrcpyOptions<Init> {
constructor(init: Init) { constructor(init: Init) {
this.value = { ...Defaults, ...init }; this.value = { ...Defaults, ...init };
if (this.value.videoSource === "camera") {
this.value.control = false;
}
if (this.value.control && this.value.clipboardAutosync) { if (this.value.control && this.value.clipboardAutosync) {
this.#clipboard = new ClipboardStream(); this.#clipboard = new ClipboardStream();
this.#ackClipboardHandler = new AckClipboardHandler(); this.#ackClipboardHandler = new AckClipboardHandler();

View file

@ -1,2 +1,2 @@
export * from "../../2_0/impl/index.js"; export * from "../../2_2/impl/index.js";
export type { Init } from "./init.js"; export type { Init } from "./init.js";

View file

@ -1 +1 @@
export * as PrevImpl from "../../2_0/impl/index.js"; export * as PrevImpl from "../../2_2/impl/index.js";

View file

@ -54,6 +54,10 @@ export class ScrcpyOptions2_3 implements ScrcpyOptions<Init> {
constructor(init: Init) { constructor(init: Init) {
this.value = { ...Defaults, ...init }; this.value = { ...Defaults, ...init };
if (this.value.videoSource === "camera") {
this.value.control = false;
}
if (this.value.control && this.value.clipboardAutosync) { if (this.value.control && this.value.clipboardAutosync) {
this.#clipboard = new ClipboardStream(); this.#clipboard = new ClipboardStream();
this.#ackClipboardHandler = new AckClipboardHandler(); this.#ackClipboardHandler = new AckClipboardHandler();

View file

@ -17,6 +17,7 @@ import type {
ScrcpyInjectTouchControlMessage, ScrcpyInjectTouchControlMessage,
ScrcpySetClipboardControlMessage, ScrcpySetClipboardControlMessage,
ScrcpyUHidCreateControlMessage, ScrcpyUHidCreateControlMessage,
ScrcpyUHidOutputDeviceMessage,
} from "../latest.js"; } from "../latest.js";
import type { Init } from "./impl/index.js"; import type { Init } from "./impl/index.js";
@ -55,13 +56,19 @@ export class ScrcpyOptions2_4 implements ScrcpyOptions<Init> {
#ackClipboardHandler: AckClipboardHandler | undefined; #ackClipboardHandler: AckClipboardHandler | undefined;
#uHidOutput: UHidOutputStream | undefined; #uHidOutput: UHidOutputStream | undefined;
get uHidOutput(): UHidOutputStream | undefined { get uHidOutput():
| ReadableStream<ScrcpyUHidOutputDeviceMessage>
| undefined {
return this.#uHidOutput; return this.#uHidOutput;
} }
constructor(init: Init) { constructor(init: Init) {
this.value = { ...Defaults, ...init }; this.value = { ...Defaults, ...init };
if (this.value.videoSource === "camera") {
this.value.control = false;
}
if (this.value.control) { if (this.value.control) {
if (this.value.clipboardAutosync) { if (this.value.clipboardAutosync) {
this.#clipboard = new ClipboardStream(); this.#clipboard = new ClipboardStream();

View file

@ -17,9 +17,9 @@ import type {
ScrcpyInjectTouchControlMessage, ScrcpyInjectTouchControlMessage,
ScrcpySetClipboardControlMessage, ScrcpySetClipboardControlMessage,
ScrcpyUHidCreateControlMessage, ScrcpyUHidCreateControlMessage,
ScrcpyUHidOutputDeviceMessage,
} from "../latest.js"; } from "../latest.js";
import type { Init } from "./impl/index.js";
import { import {
AckClipboardHandler, AckClipboardHandler,
ClipboardStream, ClipboardStream,
@ -37,7 +37,10 @@ import {
serializeUHidCreateControlMessage, serializeUHidCreateControlMessage,
setListDisplays, setListDisplays,
setListEncoders, setListEncoders,
UHidOutputStream
} from "./impl/index.js"; } from "./impl/index.js";
import type {Init} from "./impl/index.js";
export class ScrcpyOptions2_6 implements ScrcpyOptions<Init> { export class ScrcpyOptions2_6 implements ScrcpyOptions<Init> {
readonly value: Required<Init>; readonly value: Required<Init>;
@ -53,13 +56,28 @@ export class ScrcpyOptions2_6 implements ScrcpyOptions<Init> {
#ackClipboardHandler: AckClipboardHandler | undefined; #ackClipboardHandler: AckClipboardHandler | undefined;
#uHidOutput: UHidOutputStream | undefined;
get uHidOutput():
| ReadableStream<ScrcpyUHidOutputDeviceMessage>
| undefined {
return this.#uHidOutput;
}
constructor(init: Init) { constructor(init: Init) {
this.value = { ...Defaults, ...init }; this.value = { ...Defaults, ...init };
if (this.value.control && this.value.clipboardAutosync) { if (this.value.videoSource === "camera") {
this.value.control = false;
}
if (this.value.control) {
if (this.value.clipboardAutosync) {
this.#clipboard = new ClipboardStream(); this.#clipboard = new ClipboardStream();
this.#ackClipboardHandler = new AckClipboardHandler(); this.#ackClipboardHandler = new AckClipboardHandler();
} }
this.#uHidOutput = new UHidOutputStream();
}
} }
serialize(): string[] { serialize(): string[] {

View file

@ -1,17 +1,19 @@
import type { StructInit } from "@yume-chan/struct"; import type { StructInit } from "@yume-chan/struct";
import { buffer, string, struct, u16, u8 } 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"; import type { ScrcpyUHidCreateControlMessage } from "../../latest.js";
export const UHidCreateControlMessage = struct( export const UHidCreateControlMessage = /* #__PURE__ */ (() =>
struct(
{ {
type: u8, type: u8(ScrcpyControlMessageType.UHidCreate),
id: u16, id: u16,
name: string(u8), name: string(u8),
data: buffer(u16), data: buffer(u16),
}, },
{ littleEndian: false }, { littleEndian: false },
); ))();
export type UHidCreateControlMessage = StructInit< export type UHidCreateControlMessage = StructInit<
typeof UHidCreateControlMessage typeof UHidCreateControlMessage

View file

@ -17,6 +17,7 @@ import type {
ScrcpyInjectTouchControlMessage, ScrcpyInjectTouchControlMessage,
ScrcpySetClipboardControlMessage, ScrcpySetClipboardControlMessage,
ScrcpyUHidCreateControlMessage, ScrcpyUHidCreateControlMessage,
ScrcpyUHidOutputDeviceMessage,
} from "../latest.js"; } from "../latest.js";
import type { Init } from "./impl/index.js"; import type { Init } from "./impl/index.js";
@ -37,6 +38,7 @@ import {
serializeUHidCreateControlMessage, serializeUHidCreateControlMessage,
setListDisplays, setListDisplays,
setListEncoders, setListEncoders,
UHidOutputStream,
} from "./impl/index.js"; } from "./impl/index.js";
export class ScrcpyOptions2_7 implements ScrcpyOptions<Init> { export class ScrcpyOptions2_7 implements ScrcpyOptions<Init> {
@ -53,13 +55,28 @@ export class ScrcpyOptions2_7 implements ScrcpyOptions<Init> {
#ackClipboardHandler: AckClipboardHandler | undefined; #ackClipboardHandler: AckClipboardHandler | undefined;
#uHidOutput: UHidOutputStream | undefined;
get uHidOutput():
| ReadableStream<ScrcpyUHidOutputDeviceMessage>
| undefined {
return this.#uHidOutput;
}
constructor(init: Init) { constructor(init: Init) {
this.value = { ...Defaults, ...init }; this.value = { ...Defaults, ...init };
if (this.value.control && this.value.clipboardAutosync) { if (this.value.videoSource === "camera") {
this.value.control = false;
}
if (this.value.control) {
if (this.value.clipboardAutosync) {
this.#clipboard = new ClipboardStream(); this.#clipboard = new ClipboardStream();
this.#ackClipboardHandler = new AckClipboardHandler(); this.#ackClipboardHandler = new AckClipboardHandler();
} }
this.#uHidOutput = new UHidOutputStream();
}
} }
serialize(): string[] { serialize(): string[] {

View file

@ -2,8 +2,9 @@ import { ScrcpyControlMessageType } from "../../base/index.js";
import { PrevImpl } from "./prev.js"; import { PrevImpl } from "./prev.js";
export const ControlMessageTypes: readonly ScrcpyControlMessageType[] = [ export const ControlMessageTypes: readonly ScrcpyControlMessageType[] =
/* #__PURE__ */ (() => [
...PrevImpl.ControlMessageTypes, ...PrevImpl.ControlMessageTypes,
ScrcpyControlMessageType.StartApp, ScrcpyControlMessageType.StartApp,
ScrcpyControlMessageType.ResetVideo, ScrcpyControlMessageType.ResetVideo,
]; ])();

View file

@ -1,13 +1,16 @@
import { omit } from "../../utils/index.js";
import type { Init } from "./init.js"; import type { Init } from "./init.js";
import { CaptureOrientation, NewDisplay } from "./init.js"; import { CaptureOrientation } from "./init.js";
import { PrevImpl } from "./prev.js"; import { PrevImpl } from "./prev.js";
export const Defaults = { export const Defaults = /* #__PURE__ */ (() =>
...PrevImpl.Defaults, ({
captureOrientation: CaptureOrientation.Default, ...omit(PrevImpl.Defaults, "lockVideoOrientation"),
captureOrientation: CaptureOrientation.Unlocked,
angle: 0, angle: 0,
screenOffTimeout: undefined, screenOffTimeout: undefined,
listApps: false, listApps: false,
newDisplay: NewDisplay.Empty, newDisplay: undefined,
vdSystemDecorations: true, vdSystemDecorations: true,
} as const satisfies Required<Init>; }) as const satisfies Required<Init>)();

View file

@ -1,5 +1,10 @@
export * from "../../2_7/impl/index.js"; export * from "../../2_7/impl/index.js";
export { ControlMessageTypes } from "./control-message-types.js"; export { ControlMessageTypes } from "./control-message-types.js";
export { Defaults } from "./defaults.js"; export { Defaults } from "./defaults.js";
export type { Init } from "./init.js"; export {
export { EncoderRegex } from "./parse-encoder.js"; CaptureOrientation,
LockOrientation,
NewDisplay,
Orientation,
type Init,
} from "./init.js";

View file

@ -6,7 +6,7 @@ export const LockOrientation = {
Unlocked: 0, Unlocked: 0,
LockedInitial: 1, LockedInitial: 1,
LockedValue: 2, LockedValue: 2,
}; } as const;
export type LockOrientation = export type LockOrientation =
(typeof LockOrientation)[keyof typeof LockOrientation]; (typeof LockOrientation)[keyof typeof LockOrientation];
@ -16,16 +16,17 @@ export const Orientation = {
Orient90: 90, Orient90: 90,
Orient180: 180, Orient180: 180,
Orient270: 270, Orient270: 270,
}; } as const;
export type Orientation = (typeof Orientation)[keyof typeof Orientation]; export type Orientation = (typeof Orientation)[keyof typeof Orientation];
export class CaptureOrientation implements ScrcpyOptionValue { export class CaptureOrientation implements ScrcpyOptionValue {
static Default = /* #__PURE__ */ new CaptureOrientation( static Unlocked = /* #__PURE__ */ (() =>
new CaptureOrientation(
LockOrientation.Unlocked, LockOrientation.Unlocked,
Orientation.Orient0, Orientation.Orient0,
false, false,
); ))();
lock: LockOrientation; lock: LockOrientation;
orientation: Orientation; orientation: Orientation;
@ -59,7 +60,7 @@ export class CaptureOrientation implements ScrcpyOptionValue {
} }
export class NewDisplay implements ScrcpyOptionValue { export class NewDisplay implements ScrcpyOptionValue {
static Empty = /* #__PURE__ */ new NewDisplay(); static Default = /* #__PURE__ */ new NewDisplay();
width?: number | undefined; width?: number | undefined;
height?: number | undefined; height?: number | undefined;
@ -90,7 +91,7 @@ export class NewDisplay implements ScrcpyOptionValue {
this.height === undefined && this.height === undefined &&
this.dpi === undefined this.dpi === undefined
) { ) {
return undefined; return "";
} }
if (this.width === undefined) { if (this.width === undefined) {
@ -112,6 +113,9 @@ export interface Init extends Omit<PrevImpl.Init, "lockVideoOrientation"> {
listApps?: boolean; 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; vdSystemDecorations?: boolean;
} }

View file

@ -1 +0,0 @@
export const EncoderRegex = /^\s+scrcpy --encoder-name '([^']+)'$/;

View file

@ -1,2 +1,2 @@
export * as ScrcpyOptionsX_XXImpl from "./impl/index.js"; export * as ScrcpyOptions3_0Impl from "./impl/index.js";
export * from "./options.js"; export * from "./options.js";

View file

@ -17,6 +17,7 @@ import type {
ScrcpyInjectTouchControlMessage, ScrcpyInjectTouchControlMessage,
ScrcpySetClipboardControlMessage, ScrcpySetClipboardControlMessage,
ScrcpyUHidCreateControlMessage, ScrcpyUHidCreateControlMessage,
ScrcpyUHidOutputDeviceMessage,
} from "../latest.js"; } from "../latest.js";
import type { Init } from "./impl/index.js"; import type { Init } from "./impl/index.js";
@ -37,9 +38,10 @@ import {
serializeUHidCreateControlMessage, serializeUHidCreateControlMessage,
setListDisplays, setListDisplays,
setListEncoders, setListEncoders,
UHidOutputStream,
} from "./impl/index.js"; } from "./impl/index.js";
export class ScrcpyOptionsX_X implements ScrcpyOptions<Init> { export class ScrcpyOptions3_0 implements ScrcpyOptions<Init> {
readonly value: Required<Init>; readonly value: Required<Init>;
get controlMessageTypes(): readonly ScrcpyControlMessageType[] { get controlMessageTypes(): readonly ScrcpyControlMessageType[] {
@ -53,13 +55,28 @@ export class ScrcpyOptionsX_X implements ScrcpyOptions<Init> {
#ackClipboardHandler: AckClipboardHandler | undefined; #ackClipboardHandler: AckClipboardHandler | undefined;
#uHidOutput: UHidOutputStream | undefined;
get uHidOutput():
| ReadableStream<ScrcpyUHidOutputDeviceMessage>
| undefined {
return this.#uHidOutput;
}
constructor(init: Init) { constructor(init: Init) {
this.value = { ...Defaults, ...init }; this.value = { ...Defaults, ...init };
if (this.value.control && this.value.clipboardAutosync) { if (this.value.videoSource === "camera") {
this.value.control = false;
}
if (this.value.control) {
if (this.value.clipboardAutosync) {
this.#clipboard = new ClipboardStream(); this.#clipboard = new ClipboardStream();
this.#ackClipboardHandler = new AckClipboardHandler(); this.#ackClipboardHandler = new AckClipboardHandler();
} }
this.#uHidOutput = new UHidOutputStream();
}
} }
serialize(): string[] { serialize(): string[] {

View file

@ -13,7 +13,7 @@ export const AndroidMotionEventAction = {
HoverExit: 10, HoverExit: 10,
ButtonPress: 11, ButtonPress: 11,
ButtonRelease: 12, ButtonRelease: 12,
}; } as const;
export type AndroidMotionEventAction = export type AndroidMotionEventAction =
(typeof AndroidMotionEventAction)[keyof typeof AndroidMotionEventAction]; (typeof AndroidMotionEventAction)[keyof typeof AndroidMotionEventAction];
@ -27,7 +27,7 @@ export const AndroidMotionEventButton = {
Forward: 16, Forward: 16,
StylusPrimary: 32, StylusPrimary: 32,
StylusSecondary: 64, StylusSecondary: 64,
}; } as const;
export type AndroidMotionEventButton = export type AndroidMotionEventButton =
(typeof AndroidMotionEventButton)[keyof typeof AndroidMotionEventButton]; (typeof AndroidMotionEventButton)[keyof typeof AndroidMotionEventButton];

View file

@ -18,7 +18,7 @@ export const ScrcpyControlMessageType = {
OpenHardKeyboardSettings: 15, OpenHardKeyboardSettings: 15,
StartApp: 16, StartApp: 16,
ResetVideo: 17, ResetVideo: 17,
}; } as const;
export type ScrcpyControlMessageType = export type ScrcpyControlMessageType =
(typeof ScrcpyControlMessageType)[keyof typeof ScrcpyControlMessageType]; (typeof ScrcpyControlMessageType)[keyof typeof ScrcpyControlMessageType];

View file

@ -7,6 +7,7 @@ import type {
ScrcpyInjectTouchControlMessage, ScrcpyInjectTouchControlMessage,
ScrcpySetClipboardControlMessage, ScrcpySetClipboardControlMessage,
ScrcpyUHidCreateControlMessage, ScrcpyUHidCreateControlMessage,
ScrcpyUHidOutputDeviceMessage,
} from "../latest.js"; } from "../latest.js";
import type { ScrcpyAudioStreamMetadata } from "./audio.js"; import type { ScrcpyAudioStreamMetadata } from "./audio.js";
@ -22,7 +23,11 @@ export interface ScrcpyOptions<T extends object> {
value: Required<T>; value: Required<T>;
get clipboard(): ReadableStream<string> | undefined; readonly clipboard?: ReadableStream<string> | undefined;
readonly uHidOutput?:
| ReadableStream<ScrcpyUHidOutputDeviceMessage>
| undefined;
serialize(): string[]; serialize(): string[];

View file

@ -16,7 +16,17 @@ export const ScrcpyVideoCodecId = {
H264: 0x68_32_36_34, H264: 0x68_32_36_34,
H265: 0x68_32_36_35, H265: 0x68_32_36_35,
AV1: 0x00_61_76_31, AV1: 0x00_61_76_31,
}; } as const;
export type ScrcpyVideoCodecId = export type ScrcpyVideoCodecId =
(typeof ScrcpyVideoCodecId)[keyof typeof ScrcpyVideoCodecId]; (typeof ScrcpyVideoCodecId)[keyof typeof ScrcpyVideoCodecId];
export const ScrcpyVideoCodecNameMap = /* #__PURE__ */ (() => {
const result = new Map<number, string>();
for (const key in ScrcpyVideoCodecId) {
const value =
ScrcpyVideoCodecId[key as keyof typeof ScrcpyVideoCodecId];
result.set(value, key);
}
return result;
})();

View file

@ -136,7 +136,7 @@ const ObuType = {
RedundantFrameHeader: 7, RedundantFrameHeader: 7,
TileList: 8, TileList: 8,
Padding: 15, Padding: 15,
}; } as const;
type ObuType = (typeof ObuType)[keyof typeof ObuType]; type ObuType = (typeof ObuType)[keyof typeof ObuType];
@ -153,7 +153,7 @@ const ColorPrimaries = {
Smpte431: 11, Smpte431: 11,
Smpte432: 12, Smpte432: 12,
Ebu3213: 22, Ebu3213: 22,
}; } as const;
const TransferCharacteristics = { const TransferCharacteristics = {
Bt709: 1, Bt709: 1,
@ -173,7 +173,7 @@ const TransferCharacteristics = {
Smpte2084: 16, Smpte2084: 16,
Smpte428: 17, Smpte428: 17,
Hlg: 18, Hlg: 18,
}; } as const;
const MatrixCoefficients = { const MatrixCoefficients = {
Identity: 0, Identity: 0,
@ -190,7 +190,7 @@ const MatrixCoefficients = {
ChromatNcl: 12, ChromatNcl: 12,
ChromatCl: 13, ChromatCl: 13,
ICtCp: 14, ICtCp: 14,
}; } as const;
export class Av1 extends BitReader { export class Av1 extends BitReader {
static ObuType = ObuType; static ObuType = ObuType;
@ -623,13 +623,16 @@ export class Av1 extends BitReader {
// const NumPlanes = mono_chrome ? 1 : 3; // const NumPlanes = mono_chrome ? 1 : 3;
const color_description_present_flag = !!this.f1(); const color_description_present_flag = !!this.f1();
let color_primaries = Av1.ColorPrimaries.Unspecified; let color_primaries: Av1.ColorPrimaries =
let transfer_characteristics = Av1.TransferCharacteristics.Unspecified; Av1.ColorPrimaries.Unspecified;
let matrix_coefficients = Av1.MatrixCoefficients.Unspecified; let transfer_characteristics: Av1.TransferCharacteristics =
Av1.TransferCharacteristics.Unspecified;
let matrix_coefficients: Av1.MatrixCoefficients =
Av1.MatrixCoefficients.Unspecified;
if (color_description_present_flag) { if (color_description_present_flag) {
color_primaries = this.f(8); color_primaries = this.f(8) as Av1.ColorPrimaries;
transfer_characteristics = this.f(8); transfer_characteristics = this.f(8) as Av1.TransferCharacteristics;
matrix_coefficients = this.f(8); matrix_coefficients = this.f(8) as Av1.MatrixCoefficients;
} }
let color_range = false; let color_range = false;

View file

@ -6,17 +6,19 @@ import type {
AndroidKeyEventAction, AndroidKeyEventAction,
AndroidKeyEventMeta, AndroidKeyEventMeta,
} from "../android/index.js"; } from "../android/index.js";
import { ScrcpyControlMessageType } from "../base/index.js";
export const ScrcpyInjectKeyCodeControlMessage = struct( export const ScrcpyInjectKeyCodeControlMessage = /* #__PURE__ */ (() =>
struct(
{ {
type: u8, type: u8(ScrcpyControlMessageType.InjectKeyCode),
action: u8<AndroidKeyEventAction>(), action: u8<AndroidKeyEventAction>(),
keyCode: u32<AndroidKeyCode>(), keyCode: u32<AndroidKeyCode>(),
repeat: u32, repeat: u32,
metaState: u32<AndroidKeyEventMeta>(), metaState: u32<AndroidKeyEventMeta>(),
}, },
{ littleEndian: false }, { littleEndian: false },
); ))();
export type ScrcpyInjectKeyCodeControlMessage = StructInit< export type ScrcpyInjectKeyCodeControlMessage = StructInit<
typeof ScrcpyInjectKeyCodeControlMessage typeof ScrcpyInjectKeyCodeControlMessage

View file

@ -24,7 +24,7 @@ export class ScrcpyControlMessageTypeMap {
message: Omit<T, "type">, message: Omit<T, "type">,
type: T["type"], type: T["type"],
): T { ): T {
(message as T).type = this.get(type); (message as T).type = this.get(type) as ScrcpyControlMessageType;
return message as T; return message as T;
} }
} }

View file

@ -1,14 +1,17 @@
import type { StructInit } from "@yume-chan/struct"; import type { StructInit } from "@yume-chan/struct";
import { buffer, struct, u16, u8 } from "@yume-chan/struct"; import { buffer, struct, u16, u8 } from "@yume-chan/struct";
export const ScrcpyUHidInputControlMessage = struct( import { ScrcpyControlMessageType } from "../base/index.js";
export const ScrcpyUHidInputControlMessage = /* #__PURE__ */ (() =>
struct(
{ {
type: u8, type: u8(ScrcpyControlMessageType.UHidInput),
id: u16, id: u16,
data: buffer(u16), data: buffer(u16),
}, },
{ littleEndian: false }, { littleEndian: false },
); ))();
export type ScrcpyUHidInputControlMessage = StructInit< export type ScrcpyUHidInputControlMessage = StructInit<
typeof ScrcpyUHidInputControlMessage typeof ScrcpyUHidInputControlMessage

View file

@ -10,10 +10,12 @@ import type {
ScrcpyInjectScrollControlMessage, ScrcpyInjectScrollControlMessage,
ScrcpyInjectTouchControlMessage, ScrcpyInjectTouchControlMessage,
ScrcpySetClipboardControlMessage, ScrcpySetClipboardControlMessage,
ScrcpyUHidCreateControlMessage,
} from "../latest.js"; } from "../latest.js";
import type { ScrcpyInjectKeyCodeControlMessage } from "./inject-key-code.js"; import type { ScrcpyInjectKeyCodeControlMessage } from "./inject-key-code.js";
import { ScrcpyControlMessageSerializer } from "./serializer.js"; import { ScrcpyControlMessageSerializer } from "./serializer.js";
import type { ScrcpyUHidInputControlMessage } from "./uhid.js";
export class ScrcpyControlMessageWriter { export class ScrcpyControlMessageWriter {
#writer: WritableStreamDefaultWriter<Consumable<Uint8Array>>; #writer: WritableStreamDefaultWriter<Consumable<Uint8Array>>;
@ -27,25 +29,23 @@ export class ScrcpyControlMessageWriter {
this.#serializer = new ScrcpyControlMessageSerializer(options); this.#serializer = new ScrcpyControlMessageSerializer(options);
} }
async write(message: Uint8Array) { write(message: Uint8Array) {
await Consumable.WritableStream.write(this.#writer, message); return Consumable.WritableStream.write(this.#writer, message);
} }
async injectKeyCode( injectKeyCode(message: Omit<ScrcpyInjectKeyCodeControlMessage, "type">) {
message: Omit<ScrcpyInjectKeyCodeControlMessage, "type">, return this.write(this.#serializer.injectKeyCode(message));
) {
await this.write(this.#serializer.injectKeyCode(message));
} }
async injectText(text: string) { injectText(text: string) {
await this.write(this.#serializer.injectText(text)); return this.write(this.#serializer.injectText(text));
} }
/** /**
* `pressure` is a float value between 0 and 1. * `pressure` is a float value between 0 and 1.
*/ */
async injectTouch(message: Omit<ScrcpyInjectTouchControlMessage, "type">) { injectTouch(message: Omit<ScrcpyInjectTouchControlMessage, "type">) {
await this.write(this.#serializer.injectTouch(message)); return this.write(this.#serializer.injectTouch(message));
} }
/** /**
@ -67,24 +67,24 @@ export class ScrcpyControlMessageWriter {
} }
} }
async setScreenPowerMode(mode: AndroidScreenPowerMode) { setScreenPowerMode(mode: AndroidScreenPowerMode) {
await this.write(this.#serializer.setDisplayPower(mode)); return this.write(this.#serializer.setDisplayPower(mode));
} }
async expandNotificationPanel() { expandNotificationPanel() {
await this.write(this.#serializer.expandNotificationPanel()); return this.write(this.#serializer.expandNotificationPanel());
} }
async expandSettingPanel() { expandSettingPanel() {
await this.write(this.#serializer.expandSettingPanel()); return this.write(this.#serializer.expandSettingPanel());
} }
async collapseNotificationPanel() { collapseNotificationPanel() {
await this.write(this.#serializer.collapseNotificationPanel()); return this.write(this.#serializer.collapseNotificationPanel());
} }
async rotateDevice() { rotateDevice() {
await this.write(this.#serializer.rotateDevice()); return this.write(this.#serializer.rotateDevice());
} }
async setClipboard( async setClipboard(
@ -99,6 +99,29 @@ export class ScrcpyControlMessageWriter {
} }
} }
uHidCreate(message: Omit<ScrcpyUHidCreateControlMessage, "type">) {
return this.write(this.#serializer.uHidCreate(message));
}
uHidInput(message: Omit<ScrcpyUHidInputControlMessage, "type">) {
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() { releaseLock() {
this.#writer.releaseLock(); this.#writer.releaseLock();
} }

View file

@ -17,6 +17,7 @@ export * from "./2_4/index.js";
export * from "./2_5.js"; export * from "./2_5.js";
export * from "./2_6/index.js"; export * from "./2_6/index.js";
export * from "./2_7/index.js"; export * from "./2_7/index.js";
export * from "./3_0/index.js";
export * from "./android/index.js"; export * from "./android/index.js";
export * from "./base/index.js"; export * from "./base/index.js";
export * from "./codec/index.js"; export * from "./codec/index.js";

View file

@ -1,8 +1,15 @@
export { export {
BackOrScreenOnControlMessage as ScrcpyBackOrScreenOnControlMessage, BackOrScreenOnControlMessage as ScrcpyBackOrScreenOnControlMessage,
CaptureOrientation as ScrcpyCaptureOrientation,
CodecOptions as ScrcpyCodecOptions,
InjectScrollControlMessage as ScrcpyInjectScrollControlMessage, InjectScrollControlMessage as ScrcpyInjectScrollControlMessage,
InjectTouchControlMessage as ScrcpyInjectTouchControlMessage, InjectTouchControlMessage as ScrcpyInjectTouchControlMessage,
InstanceId as ScrcpyInstanceId, InstanceId as ScrcpyInstanceId,
LockOrientation as ScrcpyLockOrientation,
NewDisplay as ScrcpyNewDisplay,
Orientation as ScrcpyOrientation,
PointerId as ScrcpyPointerId,
SetClipboardControlMessage as ScrcpySetClipboardControlMessage, SetClipboardControlMessage as ScrcpySetClipboardControlMessage,
UHidCreateControlMessage as ScrcpyUHidCreateControlMessage, UHidCreateControlMessage as ScrcpyUHidCreateControlMessage,
} from "./2_7/impl/index.js"; UHidOutputDeviceMessage as ScrcpyUHidOutputDeviceMessage,
} from "./3_0/impl/index.js";

View file

@ -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;
}

View file

@ -1,2 +1,4 @@
export * from "./clamp.js";
export * from "./constants.js"; export * from "./constants.js";
export * from "./omit.js";
export * from "./wrapper.js"; export * from "./wrapper.js";

View file

@ -0,0 +1,11 @@
/* #__NO_SIDE_EFFECTS__ */
export function omit<T extends Record<string, unknown>, 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;
}

View file

@ -32,6 +32,10 @@ export class ScrcpyOptionsWrapper<T extends object>
return this.#base.clipboard; return this.#base.clipboard;
} }
get uHidOutput() {
return this.#base.uHidOutput;
}
constructor(options: ScrcpyOptions<T>) { constructor(options: ScrcpyOptions<T>) {
this.#base = options; this.#base = options;
} }

View file

@ -1,11 +1,13 @@
import type { import type {
AbortSignal, AbortSignal,
ReadableStreamIteratorOptions,
ReadableStream as ReadableStreamType, ReadableStream as ReadableStreamType,
TransformStream as TransformStreamType, TransformStream as TransformStreamType,
WritableStream as WritableStreamType, WritableStream as WritableStreamType,
} from "./types.js"; } from "./types.js";
export * 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. */ /** A controller object that allows you to abort one or more DOM requests as and when desired. */
export interface AbortController { export interface AbortController {
@ -32,13 +34,70 @@ interface GlobalExtension {
TransformStream: typeof TransformStreamType; TransformStream: typeof TransformStreamType;
} }
export const { AbortController } = globalThis as unknown as GlobalExtension;
export type ReadableStream<T> = ReadableStreamType<T>; export type ReadableStream<T> = ReadableStreamType<T>;
export type WritableStream<T> = WritableStreamType<T>; export type WritableStream<T> = WritableStreamType<T>;
export type TransformStream<I, O> = TransformStreamType<I, O>; export type TransformStream<I, O> = TransformStreamType<I, O>;
export const { const ReadableStream = /* #__PURE__ */ (() => {
AbortController, const { ReadableStream } = globalThis as unknown as GlobalExtension;
ReadableStream,
WritableStream, if (!ReadableStream.from) {
TransformStream, ReadableStream.from = function (iterable) {
} = globalThis as unknown as GlobalExtension; 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* <R>(
this: ReadableStream<R>,
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;

View file

@ -242,7 +242,7 @@ export declare class ReadableStream<out R> implements AsyncIterable<R> {
* such as an array, an async generator, or a Node.js readable stream. * such as an array, an async generator, or a Node.js readable stream.
*/ */
static from<R>( static from<R>(
asyncIterable: Iterable<R> | AsyncIterable<R> | ReadableStreamLike<R>, asyncIterable: Iterable<R> | AsyncIterable<R>,
): ReadableStream<R>; ): ReadableStream<R>;
} }
@ -253,7 +253,7 @@ export declare class ReadableStream<out R> implements AsyncIterable<R> {
*/ */
export declare interface ReadableStreamAsyncIterator<R> export declare interface ReadableStreamAsyncIterator<R>
extends AsyncIterableIterator<R> { extends AsyncIterableIterator<R> {
next(): Promise<IteratorResult<R, undefined>>; next(): Promise<IteratorResult<R, void>>;
return(value?: R): Promise<IteratorResult<R>>; return(value?: R): Promise<IteratorResult<R>>;
} }

View file

@ -42,9 +42,7 @@ export interface BufferLike {
export const EmptyUint8Array = new Uint8Array(0); export const EmptyUint8Array = new Uint8Array(0);
// Rollup doesn't support `/* #__NO_SIDE_EFFECTS__ */ export const a = () => {} export const buffer: BufferLike = function (
/* #__NO_SIDE_EFFECTS__ */
function _buffer(
lengthOrField: lengthOrField:
| string | string
| number | number
@ -259,6 +257,4 @@ function _buffer(
return reader.readExactly(length); return reader.readExactly(length);
}, },
}; };
} } as never;
export const buffer: BufferLike = _buffer as never;

View file

@ -18,11 +18,11 @@ import { bipedal } from "./bipedal.js";
import type { Field } from "./field.js"; import type { Field } from "./field.js";
export interface NumberField<T> extends Field<T, never, never> { export interface NumberField<T> extends Field<T, never, never> {
<U>(infer?: T): Field<U, never, never>; <const U>(infer?: U): Field<U, never, never>;
} }
/* #__NO_SIDE_EFFECTS__ */ /* #__NO_SIDE_EFFECTS__ */
function number<T>( function factory<T>(
size: number, size: number,
serialize: Field<T, never, never>["serialize"], serialize: Field<T, never, never>["serialize"],
deserialize: Field<T, never, never>["deserialize"], deserialize: Field<T, never, never>["deserialize"],
@ -34,7 +34,7 @@ function number<T>(
return result as never; return result as never;
} }
export const u8 = number<number>( export const u8 = factory<number>(
1, 1,
(value, { buffer, index }) => { (value, { buffer, index }) => {
buffer[index] = value; buffer[index] = value;
@ -45,7 +45,7 @@ export const u8 = number<number>(
}), }),
); );
export const s8 = number<number>( export const s8 = factory<number>(
1, 1,
(value, { buffer, index }) => { (value, { buffer, index }) => {
buffer[index] = value; buffer[index] = value;
@ -56,7 +56,7 @@ export const s8 = number<number>(
}), }),
); );
export const u16 = number<number>( export const u16 = factory<number>(
2, 2,
(value, { buffer, index, littleEndian }) => { (value, { buffer, index, littleEndian }) => {
setUint16(buffer, index, value, littleEndian); setUint16(buffer, index, value, littleEndian);
@ -67,7 +67,7 @@ export const u16 = number<number>(
}), }),
); );
export const s16 = number<number>( export const s16 = factory<number>(
2, 2,
(value, { buffer, index, littleEndian }) => { (value, { buffer, index, littleEndian }) => {
setInt16(buffer, index, value, littleEndian); setInt16(buffer, index, value, littleEndian);
@ -78,7 +78,7 @@ export const s16 = number<number>(
}), }),
); );
export const u32 = number<number>( export const u32 = factory<number>(
4, 4,
(value, { buffer, index, littleEndian }) => { (value, { buffer, index, littleEndian }) => {
setUint32(buffer, index, value, littleEndian); setUint32(buffer, index, value, littleEndian);
@ -89,7 +89,7 @@ export const u32 = number<number>(
}), }),
); );
export const s32 = number<number>( export const s32 = factory<number>(
4, 4,
(value, { buffer, index, littleEndian }) => { (value, { buffer, index, littleEndian }) => {
setInt32(buffer, index, value, littleEndian); setInt32(buffer, index, value, littleEndian);
@ -100,7 +100,7 @@ export const s32 = number<number>(
}), }),
); );
export const u64 = number<bigint>( export const u64 = factory<bigint>(
8, 8,
(value, { buffer, index, littleEndian }) => { (value, { buffer, index, littleEndian }) => {
setUint64(buffer, index, value, littleEndian); setUint64(buffer, index, value, littleEndian);
@ -111,7 +111,7 @@ export const u64 = number<bigint>(
}), }),
); );
export const s64 = number<bigint>( export const s64 = factory<bigint>(
8, 8,
(value, { buffer, index, littleEndian }) => { (value, { buffer, index, littleEndian }) => {
setInt64(buffer, index, value, littleEndian); setInt64(buffer, index, value, littleEndian);

View file

@ -33,6 +33,7 @@ export function encodeUtf8(input: string): Uint8Array {
return SharedEncoder.encode(input); return SharedEncoder.encode(input);
} }
/* #__NO_SIDE_EFFECTS__ */
export function decodeUtf8(buffer: ArrayBufferView | ArrayBuffer): string { export function decodeUtf8(buffer: ArrayBufferView | ArrayBuffer): string {
// `TextDecoder` has internal states in stream mode, // `TextDecoder` has internal states in stream mode,
// but this method is not for stream mode, so the instance can be reused // but this method is not for stream mode, so the instance can be reused

40
pnpm-lock.yaml generated
View file

@ -439,21 +439,6 @@ importers:
specifier: ^5.6.3 specifier: ^5.6.3
version: 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: libraries/stream-extra:
dependencies: dependencies:
'@yume-chan/async': '@yume-chan/async':
@ -544,6 +529,31 @@ importers:
specifier: ^2.2.3 specifier: ^2.2.3
version: 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: toolchain/test-runner:
devDependencies: devDependencies:
'@types/node': '@types/node':

View file

@ -14,6 +14,11 @@
"@rollup/plugin-node-resolve": "^15.3.0", "@rollup/plugin-node-resolve": "^15.3.0",
"@rollup/plugin-terser": "^0.4.4", "@rollup/plugin-terser": "^0.4.4",
"@rollup/plugin-typescript": "^12.1.1", "@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:^"
} }
} }

View file

@ -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";