Loading libraries/adb-backend-webusb/package.json +0 −2 Original line number Diff line number Diff line Loading @@ -27,7 +27,6 @@ "scripts": { "build": "tsc -b tsconfig.build.json", "build:watch": "tsc -b tsconfig.build.json", "//test": "jest --coverage", "lint": "eslint src/**/*.ts --fix", "prepublishOnly": "npm run build" }, Loading @@ -42,7 +41,6 @@ "@yume-chan/eslint-config": "workspace:^1.0.0", "@yume-chan/tsconfig": "workspace:^1.0.0", "eslint": "^8.31.0", "jest": "^29.3.1", "typescript": "^4.9.4" } } libraries/adb-backend-webusb/src/backend.ts +127 −77 Original line number Diff line number Diff line Loading @@ -17,11 +17,88 @@ import { type StructDeserializeStream, } from "@yume-chan/struct"; export const ADB_DEVICE_FILTER: USBDeviceFilter = { /** * `classCode`, `subclassCode` and `protocolCode` are required * for selecting correct USB configuration and interface. */ export type AdbDeviceFilter = USBDeviceFilter & Required< Pick<USBDeviceFilter, "classCode" | "subclassCode" | "protocolCode"> >; export const ADB_DEFAULT_DEVICE_FILTER = { classCode: 0xff, subclassCode: 0x42, protocolCode: 1, }; } as const satisfies AdbDeviceFilter; function alternateMatchesFilter( alternate: USBAlternateInterface, filters: AdbDeviceFilter[] ) { return filters.some( (filter) => alternate.interfaceClass === filter.classCode && alternate.interfaceSubclass === filter.subclassCode && alternate.interfaceProtocol === filter.protocolCode ); } function findUsbAlternateInterface( device: USBDevice, filters: AdbDeviceFilter[] ) { for (const configuration of device.configurations) { for (const interface_ of configuration.interfaces) { for (const alternate of interface_.alternates) { if (alternateMatchesFilter(alternate, filters)) { return { configuration, interface_, alternate }; } } } } throw new Error("No matched alternate interface found"); } /** * Find the first pair of input and output endpoints from an alternate interface. * * ADB interface only has two endpoints, one for input and one for output. */ function findUsbEndpoints(endpoints: USBEndpoint[]) { if (endpoints.length === 0) { throw new Error("No endpoints given"); } let inEndpoint: USBEndpoint | undefined; let outEndpoint: USBEndpoint | undefined; for (const endpoint of endpoints) { switch (endpoint.direction) { case "in": inEndpoint = endpoint; if (outEndpoint) { return { inEndpoint, outEndpoint }; } break; case "out": outEndpoint = endpoint; if (inEndpoint) { return { inEndpoint, outEndpoint }; } break; } } if (!inEndpoint) { throw new Error("No input endpoint found."); } if (!outEndpoint) { throw new Error("No output endpoint found."); } throw new Error("unreachable"); } class Uint8ArrayStructDeserializeStream implements StructDeserializeStream { private buffer: Uint8Array; Loading Loading @@ -162,17 +239,21 @@ export class AdbWebUsbBackend implements AdbBackend { return !!globalThis.navigator?.usb; } public static async getDevices(): Promise<AdbWebUsbBackend[]> { public static async getDevices( filters: AdbDeviceFilter[] = [ADB_DEFAULT_DEVICE_FILTER] ): Promise<AdbWebUsbBackend[]> { const devices = await window.navigator.usb.getDevices(); return devices.map((device) => new AdbWebUsbBackend(device)); return devices.map((device) => new AdbWebUsbBackend(filters, device)); } public static async requestDevice(): Promise<AdbWebUsbBackend | undefined> { public static async requestDevice( filters: AdbDeviceFilter[] = [ADB_DEFAULT_DEVICE_FILTER] ): Promise<AdbWebUsbBackend | undefined> { try { const device = await navigator.usb.requestDevice({ filters: [ADB_DEVICE_FILTER], filters, }); return new AdbWebUsbBackend(device); return new AdbWebUsbBackend(filters, device); } catch (e) { // User cancelled the device picker if (e instanceof DOMException && e.name === "NotFoundError") { Loading @@ -183,7 +264,11 @@ export class AdbWebUsbBackend implements AdbBackend { } } private _filters: AdbDeviceFilter[]; private _device: USBDevice; public get device() { return this._device; } public get serial(): string { return this._device.serialNumber!; Loading @@ -193,7 +278,8 @@ export class AdbWebUsbBackend implements AdbBackend { return this._device.productName!; } public constructor(device: USBDevice) { public constructor(filters: AdbDeviceFilter[], device: USBDevice) { this._filters = filters; this._device = device; } Loading @@ -202,17 +288,9 @@ export class AdbWebUsbBackend implements AdbBackend { await this._device.open(); } for (const configuration of this._device.configurations) { for (const interface_ of configuration.interfaces) { for (const alternate of interface_.alternates) { if ( alternate.interfaceSubclass === ADB_DEVICE_FILTER.subclassCode && alternate.interfaceClass === ADB_DEVICE_FILTER.classCode && alternate.interfaceSubclass === ADB_DEVICE_FILTER.subclassCode ) { const { configuration, interface_, alternate } = findUsbAlternateInterface(this._device, this._filters); if ( this._device.configuration?.configurationValue !== configuration.configurationValue Loading @@ -225,14 +303,11 @@ export class AdbWebUsbBackend implements AdbBackend { } if (!interface_.claimed) { await this._device.claimInterface( interface_.interfaceNumber ); await this._device.claimInterface(interface_.interfaceNumber); } if ( interface_.alternate.alternateSetting !== alternate.alternateSetting interface_.alternate.alternateSetting !== alternate.alternateSetting ) { await this._device.selectAlternateInterface( interface_.interfaceNumber, Loading @@ -240,38 +315,13 @@ export class AdbWebUsbBackend implements AdbBackend { ); } let inEndpoint: USBEndpoint | undefined; let outEndpoint: USBEndpoint | undefined; for (const endpoint of alternate.endpoints) { switch (endpoint.direction) { case "in": inEndpoint = endpoint; if (outEndpoint) { return new AdbWebUsbBackendStream( this._device, inEndpoint, outEndpoint const { inEndpoint, outEndpoint } = findUsbEndpoints( alternate.endpoints ); } break; case "out": outEndpoint = endpoint; if (inEndpoint) { return new AdbWebUsbBackendStream( this._device, inEndpoint, outEndpoint ); } break; } } } } } } throw new Error("Can not find ADB interface"); } } Loading
libraries/adb-backend-webusb/package.json +0 −2 Original line number Diff line number Diff line Loading @@ -27,7 +27,6 @@ "scripts": { "build": "tsc -b tsconfig.build.json", "build:watch": "tsc -b tsconfig.build.json", "//test": "jest --coverage", "lint": "eslint src/**/*.ts --fix", "prepublishOnly": "npm run build" }, Loading @@ -42,7 +41,6 @@ "@yume-chan/eslint-config": "workspace:^1.0.0", "@yume-chan/tsconfig": "workspace:^1.0.0", "eslint": "^8.31.0", "jest": "^29.3.1", "typescript": "^4.9.4" } }
libraries/adb-backend-webusb/src/backend.ts +127 −77 Original line number Diff line number Diff line Loading @@ -17,11 +17,88 @@ import { type StructDeserializeStream, } from "@yume-chan/struct"; export const ADB_DEVICE_FILTER: USBDeviceFilter = { /** * `classCode`, `subclassCode` and `protocolCode` are required * for selecting correct USB configuration and interface. */ export type AdbDeviceFilter = USBDeviceFilter & Required< Pick<USBDeviceFilter, "classCode" | "subclassCode" | "protocolCode"> >; export const ADB_DEFAULT_DEVICE_FILTER = { classCode: 0xff, subclassCode: 0x42, protocolCode: 1, }; } as const satisfies AdbDeviceFilter; function alternateMatchesFilter( alternate: USBAlternateInterface, filters: AdbDeviceFilter[] ) { return filters.some( (filter) => alternate.interfaceClass === filter.classCode && alternate.interfaceSubclass === filter.subclassCode && alternate.interfaceProtocol === filter.protocolCode ); } function findUsbAlternateInterface( device: USBDevice, filters: AdbDeviceFilter[] ) { for (const configuration of device.configurations) { for (const interface_ of configuration.interfaces) { for (const alternate of interface_.alternates) { if (alternateMatchesFilter(alternate, filters)) { return { configuration, interface_, alternate }; } } } } throw new Error("No matched alternate interface found"); } /** * Find the first pair of input and output endpoints from an alternate interface. * * ADB interface only has two endpoints, one for input and one for output. */ function findUsbEndpoints(endpoints: USBEndpoint[]) { if (endpoints.length === 0) { throw new Error("No endpoints given"); } let inEndpoint: USBEndpoint | undefined; let outEndpoint: USBEndpoint | undefined; for (const endpoint of endpoints) { switch (endpoint.direction) { case "in": inEndpoint = endpoint; if (outEndpoint) { return { inEndpoint, outEndpoint }; } break; case "out": outEndpoint = endpoint; if (inEndpoint) { return { inEndpoint, outEndpoint }; } break; } } if (!inEndpoint) { throw new Error("No input endpoint found."); } if (!outEndpoint) { throw new Error("No output endpoint found."); } throw new Error("unreachable"); } class Uint8ArrayStructDeserializeStream implements StructDeserializeStream { private buffer: Uint8Array; Loading Loading @@ -162,17 +239,21 @@ export class AdbWebUsbBackend implements AdbBackend { return !!globalThis.navigator?.usb; } public static async getDevices(): Promise<AdbWebUsbBackend[]> { public static async getDevices( filters: AdbDeviceFilter[] = [ADB_DEFAULT_DEVICE_FILTER] ): Promise<AdbWebUsbBackend[]> { const devices = await window.navigator.usb.getDevices(); return devices.map((device) => new AdbWebUsbBackend(device)); return devices.map((device) => new AdbWebUsbBackend(filters, device)); } public static async requestDevice(): Promise<AdbWebUsbBackend | undefined> { public static async requestDevice( filters: AdbDeviceFilter[] = [ADB_DEFAULT_DEVICE_FILTER] ): Promise<AdbWebUsbBackend | undefined> { try { const device = await navigator.usb.requestDevice({ filters: [ADB_DEVICE_FILTER], filters, }); return new AdbWebUsbBackend(device); return new AdbWebUsbBackend(filters, device); } catch (e) { // User cancelled the device picker if (e instanceof DOMException && e.name === "NotFoundError") { Loading @@ -183,7 +264,11 @@ export class AdbWebUsbBackend implements AdbBackend { } } private _filters: AdbDeviceFilter[]; private _device: USBDevice; public get device() { return this._device; } public get serial(): string { return this._device.serialNumber!; Loading @@ -193,7 +278,8 @@ export class AdbWebUsbBackend implements AdbBackend { return this._device.productName!; } public constructor(device: USBDevice) { public constructor(filters: AdbDeviceFilter[], device: USBDevice) { this._filters = filters; this._device = device; } Loading @@ -202,17 +288,9 @@ export class AdbWebUsbBackend implements AdbBackend { await this._device.open(); } for (const configuration of this._device.configurations) { for (const interface_ of configuration.interfaces) { for (const alternate of interface_.alternates) { if ( alternate.interfaceSubclass === ADB_DEVICE_FILTER.subclassCode && alternate.interfaceClass === ADB_DEVICE_FILTER.classCode && alternate.interfaceSubclass === ADB_DEVICE_FILTER.subclassCode ) { const { configuration, interface_, alternate } = findUsbAlternateInterface(this._device, this._filters); if ( this._device.configuration?.configurationValue !== configuration.configurationValue Loading @@ -225,14 +303,11 @@ export class AdbWebUsbBackend implements AdbBackend { } if (!interface_.claimed) { await this._device.claimInterface( interface_.interfaceNumber ); await this._device.claimInterface(interface_.interfaceNumber); } if ( interface_.alternate.alternateSetting !== alternate.alternateSetting interface_.alternate.alternateSetting !== alternate.alternateSetting ) { await this._device.selectAlternateInterface( interface_.interfaceNumber, Loading @@ -240,38 +315,13 @@ export class AdbWebUsbBackend implements AdbBackend { ); } let inEndpoint: USBEndpoint | undefined; let outEndpoint: USBEndpoint | undefined; for (const endpoint of alternate.endpoints) { switch (endpoint.direction) { case "in": inEndpoint = endpoint; if (outEndpoint) { return new AdbWebUsbBackendStream( this._device, inEndpoint, outEndpoint const { inEndpoint, outEndpoint } = findUsbEndpoints( alternate.endpoints ); } break; case "out": outEndpoint = endpoint; if (inEndpoint) { return new AdbWebUsbBackendStream( this._device, inEndpoint, outEndpoint ); } break; } } } } } } throw new Error("Can not find ADB interface"); } }