mirror of
https://github.com/yume-chan/ya-webadb.git
synced 2025-10-05 10:49:24 +02:00
feat(demo): add UI for manually adding websocket backends
This commit is contained in:
parent
3492e3bf71
commit
2e38ec5ce3
27 changed files with 1360 additions and 761 deletions
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
|
@ -77,5 +77,6 @@
|
|||
"files.associations": {
|
||||
"*.mdx": "markdown",
|
||||
"*.json": "jsonc"
|
||||
}
|
||||
},
|
||||
"prettier.tabWidth": 4
|
||||
}
|
||||
|
|
39
CONTRIBUTE.md
Normal file
39
CONTRIBUTE.md
Normal file
|
@ -0,0 +1,39 @@
|
|||
|
||||
## Development
|
||||
|
||||
The repository uses [Rush](https://rushjs.io/) for monorepo management.
|
||||
|
||||
### Install Rush globally
|
||||
|
||||
```sh
|
||||
$ npm i -g @microsoft/rush
|
||||
```
|
||||
|
||||
### Install dependencies
|
||||
|
||||
```sh
|
||||
$ rush update
|
||||
```
|
||||
|
||||
### Everyday commands
|
||||
|
||||
1. Build all packages:
|
||||
|
||||
```sh
|
||||
$ rush build
|
||||
```
|
||||
|
||||
2. Watch and rebuild all libraries:
|
||||
|
||||
```sh
|
||||
$ rush build:watch
|
||||
```
|
||||
|
||||
3. Start demo dev-server:
|
||||
|
||||
```sh
|
||||
$ cd apps/demo
|
||||
$ npm run dev
|
||||
```
|
||||
|
||||
Usually you need two terminals to run both 2 and 3.
|
81
README.md
81
README.md
|
@ -1,68 +1,51 @@
|
|||
# Android Debug Bridge (ADB) for Web Browsers
|
||||
# 📱 Android Debug Bridge (ADB) for Web Browsers
|
||||
|
||||
[](https://github.com/yume-chan/ya-webadb/blob/master/LICENSE)
|
||||
|
||||
Manipulate Android devices from any (supported) web browsers, even from another Android device.
|
||||
|
||||
Online demo: https://yume-chan.github.io/ya-webadb
|
||||
[🚀 Online Demo](https://yume-chan.github.io/ya-webadb)
|
||||
|
||||
## How does it work
|
||||
## Compatibility
|
||||
|
||||
**I'm working on a series of [blog posts](https://chensi.moe/blog/2020/09/28/webadb-part0-overview/) explaining the ADB protocol and my implementation in details.**
|
||||
| Connection | Chromium-based Browsers | Firefox | Node.js |
|
||||
| ------------------------------------- | ------------------------------------------ | ------- | -------- |
|
||||
| USB cable | Yes via [WebUSB] | No | Possible |
|
||||
| Wireless via [WebSocket] <sup>1</sup> | Yes | Yes | Possible |
|
||||
| Wireless via TCP | Possible via [Direct Sockets] <sup>2</sup> | No | Possible |
|
||||
|
||||
`@yume-chan/adb` is a platform-independent TypeScript implementation of the Android Debug Bridge (ADB) protocol.
|
||||
[WebUSB]: https://wicg.github.io/webusb/
|
||||
[WebSocket]: https://websockets.spec.whatwg.org/
|
||||
[Direct Sockets]: https://wicg.github.io/raw-sockets/
|
||||
|
||||
`@yume-chan/adb-backend-webusb` is a backend for `@yume-chan/adb` that uses WebUSB API.
|
||||
<sup>1</sup> Requires WebSockify softwares, see [instruction](https://github.com/yume-chan/ya-webadb/discussions/245#discussioncomment-384030) for detail.
|
||||
|
||||
See README in each package for details.
|
||||
<sup>2</sup> Under developemnt. Requires Chrome Canary (excluding Chrome for Android) and adding page URL to `chrome://flags/#restricted-api-origins`
|
||||
|
||||
## Packages
|
||||
## Security concerns
|
||||
|
||||
This repository is a monorepo containing following packages:
|
||||
Accessing USB devices (especially your phone) directly from a web page can be **very dangerous**. Firefox developers even refused to implement the WebUSB standard because they [considered it to be **harmful**](https://mozilla.github.io/standards-positions/#webusb).
|
||||
|
||||
| Package Name | Description |
|
||||
| --------------------------------------------------------------------- | ----------------------------------------------------------------- |
|
||||
| adb ([README](libraries/adb/README.md)) | TypeScript implementation of Android Debug Bridge (ADB) protocol. |
|
||||
| adb-backend-webusb ([README](libraries/adb-backend-webusb/README.md)) | Backend for `@yume-chan/adb` using WebUSB API. |
|
||||
| event ([README](libraries/event/README.md)) | Event/EventEmitter pattern. |
|
||||
| struct ([README](libraries/struct/README.md)) | C-style structure serializer and deserializer. |
|
||||
| demo ([README](apps/demo/README.md)) | Demo of `@yume-chan/adb` and `@yume-chan/adb-backend-webusb`. |
|
||||
## Features
|
||||
|
||||
## Development
|
||||
* 📁 File Management
|
||||
* 📋 List
|
||||
* ⬆ Upload
|
||||
* ⬇ Download
|
||||
* 🗑 Delete
|
||||
* 📷 Screen Capture
|
||||
* 📜 Interactiv Shell
|
||||
* ⚙ Enable ADB over WiFi
|
||||
* 📦 Install APK
|
||||
* 🎥 [Scrcpy](https://github.com/Genymobile/scrcpy) compatible client (screen mirroring and controling device)
|
||||
|
||||
The repository uses [Rush](https://rushjs.io/) for monorepo management.
|
||||
[📋 Project Roadmap](https://github.com/yume-chan/ya-webadb/issues/348)
|
||||
|
||||
### Install Rush globally
|
||||
## Contribute
|
||||
|
||||
```sh
|
||||
$ npm i -g @microsoft/rush
|
||||
```
|
||||
See [CONTRIBUTE.md](./CONTRIBUTE.md)
|
||||
|
||||
### Install dependencies
|
||||
## Credits
|
||||
|
||||
```sh
|
||||
$ rush update
|
||||
```
|
||||
|
||||
### Everyday commands
|
||||
|
||||
Build all packages:
|
||||
|
||||
```sh
|
||||
$ rush build
|
||||
```
|
||||
|
||||
Watch all libraries:
|
||||
|
||||
```sh
|
||||
$ rush build:watch
|
||||
```
|
||||
|
||||
Start demo dev-server:
|
||||
|
||||
```sh
|
||||
$ cd apps/demo
|
||||
$ npm start
|
||||
```
|
||||
|
||||
Usually you need two terminals to run both 2 and 3.
|
||||
* Google for [ADB](https://android.googlesource.com/platform/packages/modules/adb) ([Apache License 2.0](./adb.NOTICE))
|
||||
* Romain Vimont for [Scrcpy](https://github.com/Genymobile/scrcpy) ([Apache License 2.0](https://github.com/Genymobile/scrcpy/blob/master/LICENSE))
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
import { DefaultButton, Dialog, Dropdown, IDropdownOption, PrimaryButton, ProgressIndicator, Stack, StackItem, TooltipHost } from '@fluentui/react';
|
||||
import { DefaultButton, Dialog, Dropdown, IDropdownOption, PrimaryButton, ProgressIndicator, Stack, StackItem } from '@fluentui/react';
|
||||
import { Adb, AdbBackend } from '@yume-chan/adb';
|
||||
import AdbWebUsbBackend, { AdbWebCredentialStore, AdbWebUsbBackendWatcher } from '@yume-chan/adb-backend-webusb';
|
||||
import AdbDirectSocketsBackend from "@yume-chan/adb-backend-direct-sockets";
|
||||
import AdbWebUsbBackend, { AdbWebUsbBackendWatcher } from '@yume-chan/adb-backend-webusb';
|
||||
import AdbWsBackend from '@yume-chan/adb-backend-ws';
|
||||
import AdbWebCredentialStore from '@yume-chan/adb-credential-web';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { global, logger } from '../state';
|
||||
import { CommonStackTokens } from '../utils';
|
||||
import { CommonStackTokens, Icons } from '../utils';
|
||||
|
||||
const DropdownStyles = { dropdown: { width: '100%' } };
|
||||
|
||||
|
@ -52,29 +54,65 @@ function _Connect(): JSX.Element | null {
|
|||
[]
|
||||
);
|
||||
|
||||
const [wsBackendList, setWsBackendList] = useState<AdbBackend[]>([]);
|
||||
const [wsBackendList, setWsBackendList] = useState<AdbWsBackend[]>([]);
|
||||
useEffect(() => {
|
||||
const intervalId = setInterval(async () => {
|
||||
if (connecting || global.device) {
|
||||
const savedList = localStorage.getItem('ws-backend-list');
|
||||
if (!savedList) {
|
||||
return;
|
||||
}
|
||||
|
||||
const wsBackend = new AdbWsBackend("ws://localhost:15555");
|
||||
try {
|
||||
await wsBackend.connect();
|
||||
setWsBackendList([wsBackend]);
|
||||
setSelectedBackend(wsBackend);
|
||||
} catch {
|
||||
setWsBackendList([]);
|
||||
} finally {
|
||||
await wsBackend.dispose();
|
||||
}
|
||||
}, 5000);
|
||||
const parsed = JSON.parse(savedList) as { address: string; }[];
|
||||
setWsBackendList(parsed.map(x => new AdbWsBackend(x.address)));
|
||||
}, []);
|
||||
|
||||
return () => {
|
||||
clearInterval(intervalId);
|
||||
};
|
||||
}, [connecting]);
|
||||
const addWsBackend = useCallback(() => {
|
||||
const address = window.prompt('Enter the address of WebSockify server');
|
||||
if (!address) {
|
||||
return;
|
||||
}
|
||||
setWsBackendList(list => {
|
||||
const copy = list.slice();
|
||||
copy.push(new AdbWsBackend(address));
|
||||
window.localStorage.setItem('ws-backend-list', JSON.stringify(copy.map(x => ({ address: x.serial }))));
|
||||
return copy;
|
||||
});
|
||||
}, []);
|
||||
|
||||
const [tcpBackendList, setTcpBackendList] = useState<AdbDirectSocketsBackend[]>([]);
|
||||
useEffect(() => {
|
||||
if (!AdbDirectSocketsBackend.isSupported()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const savedList = localStorage.getItem('tcp-backend-list');
|
||||
if (!savedList) {
|
||||
return;
|
||||
}
|
||||
|
||||
const parsed = JSON.parse(savedList) as { address: string; port: number; }[];
|
||||
setTcpBackendList(parsed.map(x => new AdbDirectSocketsBackend(x.address, x.port)));
|
||||
}, []);
|
||||
|
||||
const addTcpBackend = useCallback(() => {
|
||||
const address = window.prompt('Enter the address of device');
|
||||
if (!address) {
|
||||
return;
|
||||
}
|
||||
|
||||
const port = window.prompt('Enter the port of device', '5555');
|
||||
if (!port) {
|
||||
return;
|
||||
}
|
||||
|
||||
const portNumber = Number.parseInt(port, 10);
|
||||
|
||||
setTcpBackendList(list => {
|
||||
const copy = list.slice();
|
||||
copy.push(new AdbDirectSocketsBackend(address, portNumber));
|
||||
window.localStorage.setItem('tcp-backend-list', JSON.stringify(copy.map(x => ({ address: x.address, port: x.port }))));
|
||||
return copy;
|
||||
});
|
||||
}, []);
|
||||
|
||||
const handleSelectedBackendChange = (
|
||||
e: React.FormEvent<HTMLDivElement>,
|
||||
|
@ -143,6 +181,28 @@ function _Connect(): JSX.Element | null {
|
|||
});
|
||||
}, [backendList]);
|
||||
|
||||
const addMenuProps = useMemo(() => {
|
||||
const items = [];
|
||||
|
||||
items.push({
|
||||
key: 'websocket',
|
||||
text: 'WebSocket',
|
||||
onClick: addWsBackend,
|
||||
});
|
||||
|
||||
if (AdbDirectSocketsBackend.isSupported()) {
|
||||
items.push({
|
||||
key: 'direct-sockets',
|
||||
text: 'Direct Sockets TCP',
|
||||
onClick: addTcpBackend,
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
items,
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Stack
|
||||
tokens={{ childrenGap: 8, padding: '0 0 8px 8px' }}
|
||||
|
@ -158,10 +218,12 @@ function _Connect(): JSX.Element | null {
|
|||
onChange={handleSelectedBackendChange}
|
||||
/>
|
||||
|
||||
{!global.device ? (
|
||||
{!global.device
|
||||
? (
|
||||
<Stack horizontal tokens={CommonStackTokens}>
|
||||
<StackItem grow shrink>
|
||||
<PrimaryButton
|
||||
iconProps={{ iconName: Icons.PlugConnected }}
|
||||
text="Connect"
|
||||
disabled={!selectedBackend}
|
||||
primary={!!selectedBackend}
|
||||
|
@ -170,21 +232,26 @@ function _Connect(): JSX.Element | null {
|
|||
/>
|
||||
</StackItem>
|
||||
<StackItem grow shrink>
|
||||
<TooltipHost
|
||||
content="WebADB can't connect to anything without your explicit permission."
|
||||
>
|
||||
<DefaultButton
|
||||
text="Add device"
|
||||
iconProps={{ iconName: Icons.AddCircle }}
|
||||
text="Add"
|
||||
split
|
||||
splitButtonAriaLabel="Add other connection type"
|
||||
menuProps={addMenuProps}
|
||||
disabled={!supported}
|
||||
primary={!selectedBackend}
|
||||
styles={{ root: { width: '100%' } }}
|
||||
onClick={requestAccess}
|
||||
/>
|
||||
</TooltipHost>
|
||||
</StackItem>
|
||||
</Stack>
|
||||
) : (
|
||||
<DefaultButton text="Disconnect" onClick={disconnect} />
|
||||
)
|
||||
: (
|
||||
<DefaultButton
|
||||
iconProps={{ iconName: Icons.PlugDisconnected }}
|
||||
text="Disconnect"
|
||||
onClick={disconnect}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Dialog
|
||||
|
|
|
@ -3,9 +3,9 @@ import { AdbPacketInit } from '@yume-chan/adb';
|
|||
import { decodeUtf8 } from '@yume-chan/adb-backend-webusb';
|
||||
import { DisposableList } from '@yume-chan/event';
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { createContext, useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
||||
import { global, logger } from "../state";
|
||||
import { withDisplayName } from '../utils';
|
||||
import { Icons, withDisplayName } from '../utils';
|
||||
import { CommandBar } from './command-bar';
|
||||
|
||||
const classNames = mergeStyleSets({
|
||||
|
@ -59,7 +59,7 @@ export const ToggleLogView = observer(() => {
|
|||
return (
|
||||
<IconButton
|
||||
checked={global.logVisible}
|
||||
iconProps={{ iconName: 'ChangeEntitlements' }}
|
||||
iconProps={{ iconName: Icons.TextGrammarError }}
|
||||
title="Toggle Log"
|
||||
onClick={global.toggleLog}
|
||||
/>
|
||||
|
@ -120,9 +120,7 @@ export const LogView = observer(({
|
|||
{
|
||||
key: 'Copy',
|
||||
text: 'Copy',
|
||||
iconProps: {
|
||||
iconName: 'Copy',
|
||||
},
|
||||
iconProps: { iconName: Icons.Copy },
|
||||
onClick: () => {
|
||||
setPackets(lines => {
|
||||
window.navigator.clipboard.writeText(lines.join('\r'));
|
||||
|
@ -133,9 +131,7 @@ export const LogView = observer(({
|
|||
{
|
||||
key: 'Clear',
|
||||
text: 'Clear',
|
||||
iconProps: {
|
||||
iconName: 'Delete',
|
||||
},
|
||||
iconProps: { iconName: Icons.Delete },
|
||||
onClick: () => {
|
||||
setPackets([]);
|
||||
},
|
||||
|
|
|
@ -9,13 +9,16 @@
|
|||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fluentui/font-icons-mdl2": "^8.1.14",
|
||||
"@fluentui/react": "^8.36.3",
|
||||
"@fluentui/react-file-type-icons": "^8.4.3",
|
||||
"@fluentui/react-hooks": "^8.3.4",
|
||||
"@fluentui/react-icons": "^2.0.154-beta.5",
|
||||
"@fluentui/react-make-styles": "^9.0.0-beta.3",
|
||||
"@yume-chan/adb": "^0.0.9",
|
||||
"@yume-chan/adb-backend-direct-sockets": "^0.0.9",
|
||||
"@yume-chan/adb-backend-webusb": "^0.0.9",
|
||||
"@yume-chan/adb-backend-ws": "^0.0.9",
|
||||
"@yume-chan/adb-credential-web": "^0.0.9",
|
||||
"@yume-chan/async": "^2.1.4",
|
||||
"@yume-chan/event": "^0.0.9",
|
||||
"@yume-chan/scrcpy": "^0.0.9",
|
||||
|
|
|
@ -1,40 +1,49 @@
|
|||
import { IComponentAsProps, IconButton, INavButtonProps, initializeIcons, mergeStyles, mergeStyleSets, Nav, Stack, StackItem } from "@fluentui/react";
|
||||
import { IComponentAsProps, IconButton, INavButtonProps, mergeStyles, mergeStyleSets, Nav, registerIcons, Stack, StackItem } from "@fluentui/react";
|
||||
import type { AppProps } from 'next/app';
|
||||
import Link from 'next/link';
|
||||
import { useRouter } from 'next/router';
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { Connect, ErrorDialogProvider, LogView, ToggleLogView } from "../components";
|
||||
import { register } from '../utils/icons';
|
||||
import '../styles/globals.css';
|
||||
import { Icons } from "../utils";
|
||||
|
||||
initializeIcons();
|
||||
register();
|
||||
|
||||
const ROUTES = [
|
||||
{
|
||||
url: '/',
|
||||
icon: Icons.Bookmark,
|
||||
name: 'README',
|
||||
},
|
||||
{
|
||||
url: '/device-info',
|
||||
icon: Icons.Phone,
|
||||
name: 'Device Info',
|
||||
},
|
||||
{
|
||||
url: '/file-manager',
|
||||
icon: Icons.Folder,
|
||||
name: 'File Manager',
|
||||
},
|
||||
{
|
||||
url: '/framebuffer',
|
||||
icon: Icons.Camera,
|
||||
name: 'Screen Capture',
|
||||
},
|
||||
{
|
||||
url: '/shell',
|
||||
icon: Icons.WindowConsole,
|
||||
name: 'Interactive Shell',
|
||||
},
|
||||
{
|
||||
url: '/scrcpy',
|
||||
icon: Icons.PhoneLaptop,
|
||||
name: 'Scrcpy',
|
||||
},
|
||||
{
|
||||
url: '/tcpip',
|
||||
icon: Icons.WifiSettings,
|
||||
name: 'ADB over WiFi',
|
||||
},
|
||||
];
|
||||
|
@ -62,7 +71,7 @@ function MyApp({ Component, pageProps }: AppProps) {
|
|||
textAlign: 'center',
|
||||
},
|
||||
'left-column': {
|
||||
width: 250,
|
||||
width: 270,
|
||||
paddingRight: 8,
|
||||
borderRight: '1px solid rgb(243, 242, 241)',
|
||||
overflow: 'auto',
|
||||
|
@ -89,7 +98,7 @@ function MyApp({ Component, pageProps }: AppProps) {
|
|||
<IconButton
|
||||
checked={leftPanelVisible}
|
||||
title="Toggle Menu"
|
||||
iconProps={{ iconName: 'GlobalNavButton' }}
|
||||
iconProps={{ iconName: Icons.Navigation }}
|
||||
onClick={toggleLeftPanel}
|
||||
/>
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ import Head from 'next/head';
|
|||
import React from "react";
|
||||
import { ExternalLink } from "../components";
|
||||
import { global } from '../state';
|
||||
import { RouteStackProps } from "../utils";
|
||||
import { Icons, RouteStackProps } from "../utils";
|
||||
|
||||
const KNOWN_FEATURES: Record<string, string> = {
|
||||
'shell_v2': `"shell" command now supports separating child process's stdout and stderr, and returning exit code`,
|
||||
|
@ -93,7 +93,7 @@ const DeviceInfo: NextPage = () => {
|
|||
<span>{feature}</span>
|
||||
{KNOWN_FEATURES[feature] && (
|
||||
<TooltipHost content={<span>{KNOWN_FEATURES[feature]}</span>}>
|
||||
<Icon style={{ marginLeft: 4 }} iconName="Unknown" />
|
||||
<Icon style={{ marginLeft: 4 }} iconName={Icons.Info} />
|
||||
</TooltipHost>
|
||||
)}
|
||||
</span>
|
||||
|
|
|
@ -2,7 +2,7 @@ import { Breadcrumb, concatStyleSets, ContextualMenu, ContextualMenuItem, Detail
|
|||
import { FileIconType, getFileTypeIconProps, initializeFileTypeIcons } from '@fluentui/react-file-type-icons';
|
||||
import { useConst } from '@fluentui/react-hooks';
|
||||
import { AdbSyncEntryResponse, AdbSyncMaxPacketSize, LinuxFileType } from '@yume-chan/adb';
|
||||
import { autorun, makeAutoObservable, observable, runInAction } from "mobx";
|
||||
import { action, autorun, makeAutoObservable, observable, runInAction } from "mobx";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { NextPage } from "next";
|
||||
import Head from "next/head";
|
||||
|
@ -11,7 +11,7 @@ import path from 'path';
|
|||
import React, { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';
|
||||
import { CommandBar } from '../components';
|
||||
import { global } from '../state';
|
||||
import { asyncEffect, chunkFile, formatSize, formatSpeed, pickFile, RouteStackProps, useSpeed } from '../utils';
|
||||
import { asyncEffect, chunkFile, formatSize, formatSpeed, Icons, pickFile, RouteStackProps, useSpeed } from '../utils';
|
||||
|
||||
let StreamSaver: typeof import('streamsaver');
|
||||
if (typeof window !== 'undefined') {
|
||||
|
@ -93,6 +93,16 @@ class FileManagerState {
|
|||
sortDescending = false;
|
||||
startItemIndexInView = 0;
|
||||
|
||||
uploading = false;
|
||||
uploadPath: string | undefined = undefined;
|
||||
uploadedSize = 0;
|
||||
uploadTotalSize = 0;
|
||||
debouncedUploadedSize = 0;
|
||||
uploadSpeed = 0;
|
||||
|
||||
selectedItems: ListItem[] = [];
|
||||
contextMenuTarget: MouseEvent | undefined = undefined;
|
||||
|
||||
get breadcrumbItems(): IBreadcrumbItem[] {
|
||||
let part = '';
|
||||
const list: IBreadcrumbItem[] = this.path.split('/').filter(Boolean).map(segment => {
|
||||
|
@ -118,6 +128,95 @@ class FileManagerState {
|
|||
return list;
|
||||
}
|
||||
|
||||
get menuItems() {
|
||||
let result: IContextualMenuItem[] = [];
|
||||
|
||||
switch (this.selectedItems.length) {
|
||||
case 0:
|
||||
result.push({
|
||||
key: 'upload',
|
||||
text: 'Upload',
|
||||
iconProps: {
|
||||
iconName: Icons.CloudArrowUp,
|
||||
style: { height: 20, fontSize: 20, lineHeight: 1.5 }
|
||||
},
|
||||
disabled: !global.device,
|
||||
onClick() {
|
||||
(async () => {
|
||||
const files = await pickFile({ multiple: true });
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
const file = files.item(i)!;
|
||||
await state.upload(file);
|
||||
}
|
||||
})();
|
||||
|
||||
return false;
|
||||
}
|
||||
});
|
||||
break;
|
||||
case 1:
|
||||
if (this.selectedItems[0].type === LinuxFileType.File) {
|
||||
result.push({
|
||||
key: 'download',
|
||||
text: 'Download',
|
||||
iconProps: {
|
||||
iconName: Icons.CloudArrowDown,
|
||||
style: { height: 20, fontSize: 20, lineHeight: 1.5 }
|
||||
},
|
||||
onClick() {
|
||||
(async () => {
|
||||
const sync = await global.device!.sync();
|
||||
try {
|
||||
const itemPath = path.resolve(state.path, this.selectedItems[0].name!);
|
||||
const readableStream = createReadableStreamFromBufferIterator(sync.read(itemPath));
|
||||
|
||||
const writeableStream = StreamSaver!.createWriteStream(this.selectedItems[0].name!, {
|
||||
size: this.selectedItems[0].size,
|
||||
});
|
||||
await readableStream.pipeTo(writeableStream);
|
||||
} catch (e) {
|
||||
global.showErrorDialog(e instanceof Error ? e.message : `${e}`);
|
||||
} finally {
|
||||
sync.dispose();
|
||||
}
|
||||
})();
|
||||
return false;
|
||||
},
|
||||
});
|
||||
}
|
||||
default:
|
||||
result.push({
|
||||
key: 'delete',
|
||||
text: 'Delete',
|
||||
iconProps: {
|
||||
iconName: Icons.Delete,
|
||||
style: { height: 20, fontSize: 20, lineHeight: 1.5 }
|
||||
},
|
||||
onClick() {
|
||||
(async () => {
|
||||
try {
|
||||
for (const item of this.selectedItems) {
|
||||
const output = await global.device!.rm(path.resolve(state.path, item.name!));
|
||||
if (output) {
|
||||
global.showErrorDialog(output);
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
global.showErrorDialog(e instanceof Error ? e.message : `${e}`);
|
||||
} finally {
|
||||
state.loadFiles();
|
||||
}
|
||||
})();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
get sortedList() {
|
||||
const list = this.items.slice();
|
||||
list.sort((a, b) => {
|
||||
|
@ -153,7 +252,7 @@ class FileManagerState {
|
|||
{
|
||||
key: 'type',
|
||||
name: 'File Type',
|
||||
iconName: 'Page',
|
||||
iconName: Icons.Document20,
|
||||
isIconOnly: true,
|
||||
minWidth: 20,
|
||||
maxWidth: 20,
|
||||
|
@ -342,10 +441,73 @@ class FileManagerState {
|
|||
sync.dispose();
|
||||
}
|
||||
});
|
||||
|
||||
upload = async (file: File) => {
|
||||
const sync = await global.device!.sync();
|
||||
try {
|
||||
const itemPath = path.resolve(state.path!, file.name);
|
||||
runInAction(() => {
|
||||
this.uploading = true;
|
||||
this.uploadPath = file.name;
|
||||
this.uploadedSize = 0;
|
||||
this.uploadTotalSize = file.size;
|
||||
this.debouncedUploadedSize = 0;
|
||||
this.uploadSpeed = 0;
|
||||
});
|
||||
|
||||
const intervalId = setInterval(action(() => {
|
||||
this.uploadSpeed = this.uploadedSize - this.debouncedUploadedSize;
|
||||
this.debouncedUploadedSize = this.uploadedSize;
|
||||
}), 1000);
|
||||
|
||||
try {
|
||||
await sync.write(
|
||||
itemPath,
|
||||
chunkFile(file, AdbSyncMaxPacketSize),
|
||||
(LinuxFileType.File << 12) | 0o666,
|
||||
file.lastModified / 1000,
|
||||
action((uploaded) => {
|
||||
this.uploadedSize = uploaded;
|
||||
}),
|
||||
);
|
||||
runInAction(() => {
|
||||
this.uploadSpeed = this.uploadedSize - this.debouncedUploadedSize;
|
||||
this.debouncedUploadedSize = this.uploadedSize;
|
||||
});
|
||||
} finally {
|
||||
clearInterval(intervalId);
|
||||
}
|
||||
} catch (e) {
|
||||
global.showErrorDialog(e instanceof Error ? e.message : `${e}`);
|
||||
} finally {
|
||||
sync.dispose();
|
||||
state.loadFiles();
|
||||
runInAction(() => {
|
||||
this.uploading = false;
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const state = new FileManagerState();
|
||||
|
||||
const UploadDialog = observer(() => {
|
||||
return (
|
||||
<Dialog
|
||||
hidden={!state.uploading}
|
||||
dialogContentProps={{
|
||||
title: 'Uploading...',
|
||||
subText: state.uploadPath
|
||||
}}
|
||||
>
|
||||
<ProgressIndicator
|
||||
description={formatSpeed(state.debouncedUploadedSize, state.uploadTotalSize, state.uploadSpeed)}
|
||||
percentComplete={state.uploadedSize / state.uploadTotalSize}
|
||||
/>
|
||||
</Dialog>
|
||||
);
|
||||
});
|
||||
|
||||
const FileManager: NextPage = (): JSX.Element | null => {
|
||||
useEffect(() => {
|
||||
runInAction(() => {
|
||||
|
@ -425,141 +587,34 @@ const FileManager: NextPage = (): JSX.Element | null => {
|
|||
}
|
||||
}, [previewImage]);
|
||||
|
||||
const [selectedItems, setSelectedItems] = useState<ListItem[]>([]);
|
||||
const selection = useConst(() => new Selection({
|
||||
onSelectionChanged() {
|
||||
const selectedItems = selection.getSelection() as ListItem[];
|
||||
setSelectedItems(selectedItems);
|
||||
runInAction(() => {
|
||||
state.selectedItems = selectedItems;
|
||||
});
|
||||
},
|
||||
}));
|
||||
|
||||
const [uploading, setUploading] = useState(false);
|
||||
const [uploadPath, setUploadPath] = useState('');
|
||||
const [uploadedSize, setUploadedSize] = useState(0);
|
||||
const [uploadTotalSize, setUploadTotalSize] = useState(0);
|
||||
const [debouncedUploadedSize, uploadSpeed] = useSpeed(uploadedSize, uploadTotalSize);
|
||||
const upload = useCallback(async (file: File) => {
|
||||
const sync = await global.device!.sync();
|
||||
try {
|
||||
const itemPath = path.resolve(state.path!, file.name);
|
||||
setUploading(true);
|
||||
setUploadPath(file.name);
|
||||
setUploadTotalSize(file.size);
|
||||
await sync.write(
|
||||
itemPath,
|
||||
chunkFile(file, AdbSyncMaxPacketSize),
|
||||
(LinuxFileType.File << 12) | 0o666,
|
||||
file.lastModified / 1000,
|
||||
setUploadedSize,
|
||||
);
|
||||
} catch (e) {
|
||||
global.showErrorDialog(e instanceof Error ? e.message : `${e}`);
|
||||
} finally {
|
||||
sync.dispose();
|
||||
state.loadFiles();
|
||||
setUploading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const [menuItems, setMenuItems] = useState<IContextualMenuItem[]>([]);
|
||||
useEffect(() => {
|
||||
let result: IContextualMenuItem[] = [];
|
||||
|
||||
switch (selectedItems.length) {
|
||||
case 0:
|
||||
result.push({
|
||||
key: 'upload',
|
||||
text: 'Upload',
|
||||
iconProps: { iconName: 'Upload' },
|
||||
disabled: !global.device,
|
||||
onClick() {
|
||||
(async () => {
|
||||
const files = await pickFile({ multiple: true });
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
const file = files.item(i)!;
|
||||
await upload(file);
|
||||
}
|
||||
})();
|
||||
|
||||
return false;
|
||||
}
|
||||
});
|
||||
break;
|
||||
case 1:
|
||||
if (selectedItems[0].type === LinuxFileType.File) {
|
||||
result.push({
|
||||
key: 'download',
|
||||
text: 'Download',
|
||||
iconProps: { iconName: 'Download' },
|
||||
onClick() {
|
||||
(async () => {
|
||||
const sync = await global.device!.sync();
|
||||
try {
|
||||
const itemPath = path.resolve(state.path, selectedItems[0].name!);
|
||||
const readableStream = createReadableStreamFromBufferIterator(sync.read(itemPath));
|
||||
|
||||
const writeableStream = StreamSaver!.createWriteStream(selectedItems[0].name!, {
|
||||
size: selectedItems[0].size,
|
||||
});
|
||||
await readableStream.pipeTo(writeableStream);
|
||||
} catch (e) {
|
||||
global.showErrorDialog(e instanceof Error ? e.message : `${e}`);
|
||||
} finally {
|
||||
sync.dispose();
|
||||
}
|
||||
})();
|
||||
return false;
|
||||
},
|
||||
});
|
||||
}
|
||||
default:
|
||||
result.push({
|
||||
key: 'delete',
|
||||
text: 'Delete',
|
||||
iconProps: { iconName: 'Delete' },
|
||||
onClick() {
|
||||
(async () => {
|
||||
try {
|
||||
for (const item of selectedItems) {
|
||||
const output = await global.device!.rm(path.resolve(state.path, item.name!));
|
||||
if (output) {
|
||||
global.showErrorDialog(output);
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
global.showErrorDialog(e instanceof Error ? e.message : `${e}`);
|
||||
} finally {
|
||||
state.loadFiles();
|
||||
}
|
||||
})();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
setMenuItems(result);
|
||||
}, [selectedItems, upload]);
|
||||
|
||||
const [contextMenuTarget, setContextMenuTarget] = useState<MouseEvent>();
|
||||
const showContextMenu = useCallback((
|
||||
_item?: AdbSyncEntryResponse,
|
||||
_index?: number,
|
||||
item?: AdbSyncEntryResponse,
|
||||
index?: number,
|
||||
e?: Event
|
||||
) => {
|
||||
if (!e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (menuItems.length) {
|
||||
setContextMenuTarget(e as MouseEvent);
|
||||
if (state.menuItems.length) {
|
||||
runInAction(() => {
|
||||
state.contextMenuTarget = e as MouseEvent;
|
||||
});
|
||||
}
|
||||
|
||||
return false;
|
||||
}, [menuItems]);
|
||||
}, []);
|
||||
const hideContextMenu = useCallback(() => {
|
||||
setContextMenuTarget(undefined);
|
||||
runInAction(() => state.contextMenuTarget = undefined);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
|
@ -568,7 +623,7 @@ const FileManager: NextPage = (): JSX.Element | null => {
|
|||
<title>File Manager - WebADB</title>
|
||||
</Head>
|
||||
|
||||
<CommandBar items={menuItems} />
|
||||
<CommandBar items={state.menuItems} />
|
||||
|
||||
<StackItem grow styles={{
|
||||
root: {
|
||||
|
@ -609,26 +664,15 @@ const FileManager: NextPage = (): JSX.Element | null => {
|
|||
)}
|
||||
|
||||
<ContextualMenu
|
||||
items={menuItems}
|
||||
hidden={!contextMenuTarget}
|
||||
items={state.menuItems}
|
||||
hidden={!state.contextMenuTarget}
|
||||
directionalHint={DirectionalHint.bottomLeftEdge}
|
||||
target={contextMenuTarget}
|
||||
target={state.contextMenuTarget}
|
||||
onDismiss={hideContextMenu}
|
||||
contextualMenuItemAs={props => <ContextualMenuItem {...props} hasIcons={false} />}
|
||||
/>
|
||||
|
||||
<Dialog
|
||||
hidden={!uploading}
|
||||
dialogContentProps={{
|
||||
title: 'Uploading...',
|
||||
subText: uploadPath
|
||||
}}
|
||||
>
|
||||
<ProgressIndicator
|
||||
description={formatSpeed(debouncedUploadedSize, uploadTotalSize, uploadSpeed)}
|
||||
percentComplete={uploadedSize / uploadTotalSize}
|
||||
/>
|
||||
</Dialog>
|
||||
<UploadDialog />
|
||||
</StackItem>
|
||||
</Stack>
|
||||
);
|
||||
|
|
|
@ -7,7 +7,7 @@ import Head from "next/head";
|
|||
import React, { useCallback, useEffect, useRef } from 'react';
|
||||
import { CommandBar, DemoMode, DeviceView } from '../components';
|
||||
import { global } from "../state";
|
||||
import { RouteStackProps } from "../utils";
|
||||
import { Icons, RouteStackProps } from "../utils";
|
||||
|
||||
class FrameBufferState {
|
||||
width = 0;
|
||||
|
@ -43,9 +43,7 @@ const FrameBuffer: NextPage = (): JSX.Element | null => {
|
|||
}
|
||||
|
||||
try {
|
||||
const start = window.performance.now();
|
||||
const framebuffer = await global.device.framebuffer();
|
||||
const end = window.performance.now();
|
||||
state.setImage(framebuffer);
|
||||
} catch (e) {
|
||||
global.showErrorDialog(e instanceof Error ? e.message : `${e}`);
|
||||
|
@ -68,14 +66,14 @@ const FrameBuffer: NextPage = (): JSX.Element | null => {
|
|||
{
|
||||
key: 'start',
|
||||
disabled: !global.device,
|
||||
iconProps: { iconName: 'Camera' },
|
||||
iconProps: { iconName: Icons.Camera, style: { height: 20, fontSize: 20, lineHeight: 1.5 } },
|
||||
text: 'Capture',
|
||||
onClick: capture,
|
||||
},
|
||||
{
|
||||
key: 'Save',
|
||||
disabled: !state.imageData,
|
||||
iconProps: { iconName: 'Save' },
|
||||
iconProps: { iconName: Icons.Save, style: { height: 20, fontSize: 20, lineHeight: 1.5 } },
|
||||
text: 'Save',
|
||||
onClick: () => {
|
||||
const canvas = canvasRef.current;
|
||||
|
@ -95,14 +93,14 @@ const FrameBuffer: NextPage = (): JSX.Element | null => {
|
|||
const commandBarFarItems = computed((): ICommandBarItemProps[] => [
|
||||
{
|
||||
key: 'DemoMode',
|
||||
iconProps: { iconName: 'Personalize' },
|
||||
iconProps: { iconName: Icons.Wand, style: { height: 20, fontSize: 20, lineHeight: 1.5 } },
|
||||
checked: state.demoModeVisible,
|
||||
text: 'Demo Mode Settings',
|
||||
onClick: state.toggleDemoModeVisible,
|
||||
},
|
||||
{
|
||||
key: 'info',
|
||||
iconProps: { iconName: 'Info' },
|
||||
iconProps: { iconName: Icons.Info, style: { height: 20, fontSize: 20, lineHeight: 1.5 } },
|
||||
iconOnly: true,
|
||||
tooltipHostProps: {
|
||||
content: 'Use ADB FrameBuffer command to capture a full-size, high-resolution screenshot.',
|
||||
|
|
|
@ -9,7 +9,7 @@ import Head from "next/head";
|
|||
import React, { useEffect, useState } from "react";
|
||||
import { DemoMode, DeviceView, DeviceViewRef, ExternalLink } from "../components";
|
||||
import { global } from "../state";
|
||||
import { CommonStackTokens, formatSpeed, RouteStackProps } from "../utils";
|
||||
import { CommonStackTokens, formatSpeed, Icons, RouteStackProps } from "../utils";
|
||||
|
||||
const SERVER_URL = new URL('@yume-chan/scrcpy/bin/scrcpy-server?url', import.meta.url).toString();
|
||||
|
||||
|
@ -201,14 +201,14 @@ class ScrcpyPageState {
|
|||
result.push({
|
||||
key: 'start',
|
||||
disabled: !global.device,
|
||||
iconProps: { iconName: 'Play' },
|
||||
iconProps: { iconName: Icons.Play },
|
||||
text: 'Start',
|
||||
onClick: this.start as VoidFunction,
|
||||
});
|
||||
} else {
|
||||
result.push({
|
||||
key: 'stop',
|
||||
iconProps: { iconName: 'Stop' },
|
||||
iconProps: { iconName: Icons.Stop },
|
||||
text: 'Stop',
|
||||
onClick: this.stop,
|
||||
});
|
||||
|
@ -217,7 +217,7 @@ class ScrcpyPageState {
|
|||
result.push({
|
||||
key: 'fullscreen',
|
||||
disabled: !this.running,
|
||||
iconProps: { iconName: 'Fullscreen' },
|
||||
iconProps: { iconName: Icons.FullScreenMaximize },
|
||||
text: 'Fullscreen',
|
||||
onClick: () => { this.deviceView?.enterFullscreen(); },
|
||||
});
|
||||
|
@ -238,7 +238,7 @@ class ScrcpyPageState {
|
|||
},
|
||||
{
|
||||
key: 'DemoMode',
|
||||
iconProps: { iconName: 'Personalize' },
|
||||
iconProps: { iconName: Icons.Wand },
|
||||
checked: this.demoModeVisible,
|
||||
text: 'Demo Mode Settings',
|
||||
onClick: action(() => {
|
||||
|
@ -247,7 +247,7 @@ class ScrcpyPageState {
|
|||
},
|
||||
{
|
||||
key: 'info',
|
||||
iconProps: { iconName: 'Info' },
|
||||
iconProps: { iconName: Icons.Info },
|
||||
iconOnly: true,
|
||||
tooltipHostProps: {
|
||||
content: (
|
||||
|
@ -351,20 +351,50 @@ class ScrcpyPageState {
|
|||
runInAction(() => {
|
||||
this.serverTotalSize = 0;
|
||||
this.serverDownloadedSize = 0;
|
||||
this.debouncedServerDownloadedSize = 0;
|
||||
this.serverUploadedSize = 0;
|
||||
this.debouncedServerUploadedSize = 0;
|
||||
this.connecting = true;
|
||||
});
|
||||
|
||||
const serverBuffer = await fetchServer(action(([downloaded, total]) => {
|
||||
let intervalId = setInterval(action(() => {
|
||||
this.serverDownloadSpeed = this.serverDownloadedSize - this.debouncedServerDownloadedSize;
|
||||
this.debouncedServerDownloadedSize = this.serverDownloadedSize;
|
||||
}), 1000);
|
||||
|
||||
let serverBuffer: ArrayBuffer;
|
||||
|
||||
try {
|
||||
serverBuffer = await fetchServer(action(([downloaded, total]) => {
|
||||
this.serverDownloadedSize = downloaded;
|
||||
this.serverTotalSize = total;
|
||||
}));
|
||||
runInAction(() => {
|
||||
this.serverDownloadSpeed = this.serverDownloadedSize - this.debouncedServerDownloadedSize;
|
||||
this.debouncedServerDownloadedSize = this.serverDownloadedSize;
|
||||
});
|
||||
} finally {
|
||||
clearInterval(intervalId);
|
||||
}
|
||||
|
||||
intervalId = setInterval(action(() => {
|
||||
this.serverUploadSpeed = this.serverUploadedSize - this.debouncedServerUploadedSize;
|
||||
this.debouncedServerUploadedSize = this.serverUploadedSize;
|
||||
}), 1000);
|
||||
|
||||
try {
|
||||
await pushServer(global.device, serverBuffer, {
|
||||
onProgress: action((progress) => {
|
||||
this.serverUploadedSize = progress;
|
||||
}),
|
||||
});
|
||||
runInAction(() => {
|
||||
this.serverUploadSpeed = this.serverUploadedSize - this.debouncedServerUploadedSize;
|
||||
this.debouncedServerUploadedSize = this.serverUploadedSize;
|
||||
});
|
||||
} finally {
|
||||
clearInterval(intervalId);
|
||||
}
|
||||
|
||||
const encoders = await ScrcpyClient.getEncoders(
|
||||
global.device,
|
||||
|
@ -685,19 +715,19 @@ const Scrcpy: NextPage = () => {
|
|||
<Stack verticalFill horizontalAlign="center" style={{ background: '#999' }}>
|
||||
<Stack verticalFill horizontal style={{ width: '100%', maxWidth: 300 }} horizontalAlign="space-evenly" verticalAlign="center">
|
||||
<IconButton
|
||||
iconProps={{ iconName: 'Play' }}
|
||||
iconProps={{ iconName: Icons.Play }}
|
||||
style={{ transform: 'rotate(180deg)', color: 'white' }}
|
||||
onPointerDown={state.handleBackPointerDown}
|
||||
onPointerUp={state.handleBackPointerUp}
|
||||
/>
|
||||
<IconButton
|
||||
iconProps={{ iconName: 'LocationCircle' }}
|
||||
iconProps={{ iconName: Icons.Circle }}
|
||||
style={{ color: 'white' }}
|
||||
onPointerDown={state.handleHomePointerDown}
|
||||
onPointerUp={state.handleHomePointerUp}
|
||||
/>
|
||||
<IconButton
|
||||
iconProps={{ iconName: 'Stop' }}
|
||||
iconProps={{ iconName: Icons.Stop }}
|
||||
style={{ color: 'white' }}
|
||||
onPointerDown={state.handleAppSwitchPointerDown}
|
||||
onPointerUp={state.handleAppSwitchPointerUp}
|
||||
|
@ -778,7 +808,7 @@ const Scrcpy: NextPage = () => {
|
|||
<>
|
||||
<span>Use forward connection{' '}</span>
|
||||
<TooltipHost content="Old Android devices may not support reverse connection when using ADB over WiFi">
|
||||
<Icon iconName="Info" />
|
||||
<Icon iconName={Icons.Info} />
|
||||
</TooltipHost>
|
||||
</>
|
||||
}
|
||||
|
|
|
@ -3,10 +3,10 @@ import { reaction } from "mobx";
|
|||
import { observer } from "mobx-react-lite";
|
||||
import { NextPage } from "next";
|
||||
import Head from "next/head";
|
||||
import React, { CSSProperties, useCallback, useContext, useEffect, useRef, useState } from 'react';
|
||||
import React, { CSSProperties, useCallback, useEffect, useRef, useState } from 'react';
|
||||
import 'xterm/css/xterm.css';
|
||||
import { global } from "../state";
|
||||
import { ResizeObserver, RouteStackProps } from '../utils';
|
||||
import { Icons, ResizeObserver, RouteStackProps } from '../utils';
|
||||
|
||||
let terminal: import('../components/terminal').AdbTerminal;
|
||||
if (typeof window !== 'undefined') {
|
||||
|
@ -19,8 +19,8 @@ const ResizeObserverStyle: CSSProperties = {
|
|||
height: '100%',
|
||||
};
|
||||
|
||||
const UpIconProps = { iconName: 'ChevronUp' };
|
||||
const DownIconProps = { iconName: 'ChevronDown' };
|
||||
const UpIconProps = { iconName: Icons.ChevronUp };
|
||||
const DownIconProps = { iconName: Icons.ChevronDown };
|
||||
|
||||
const Shell: NextPage = (): JSX.Element | null => {
|
||||
const [searchKeyword, setSearchKeyword] = useState('');
|
||||
|
|
|
@ -6,7 +6,7 @@ import Head from "next/head";
|
|||
import React, { useCallback, useEffect } from "react";
|
||||
import { ExternalLink } from "../components";
|
||||
import { global } from "../state";
|
||||
import { asyncEffect, RouteStackProps } from "../utils";
|
||||
import { asyncEffect, Icons, RouteStackProps } from "../utils";
|
||||
|
||||
class TcpIpState {
|
||||
initial = true;
|
||||
|
@ -42,14 +42,14 @@ class TcpIpState {
|
|||
{
|
||||
key: 'refresh',
|
||||
disabled: !global.device,
|
||||
iconProps: { iconName: 'Refresh' },
|
||||
iconProps: { iconName: Icons.ArrowClockwise },
|
||||
text: 'Refresh',
|
||||
onClick: this.queryInfo as () => void,
|
||||
onClick: this.queryInfo as VoidFunction,
|
||||
},
|
||||
{
|
||||
key: 'apply',
|
||||
disabled: !global.device,
|
||||
iconProps: { iconName: 'Save' },
|
||||
iconProps: { iconName: Icons.Save },
|
||||
text: 'Apply',
|
||||
onClick: this.applyServicePort,
|
||||
}
|
||||
|
|
80
apps/demo/utils/icons.tsx
Normal file
80
apps/demo/utils/icons.tsx
Normal file
|
@ -0,0 +1,80 @@
|
|||
import { registerIcons } from "@fluentui/react";
|
||||
import { AddCircleRegular, ArrowClockwiseRegular, ArrowSortDownRegular, ArrowSortUpRegular, BookmarkRegular, CameraRegular, CheckmarkRegular, ChevronDownRegular, ChevronRightRegular, ChevronUpRegular, CircleRegular, CloudArrowDownRegular, CloudArrowUpRegular, CopyRegular, DeleteRegular, DocumentRegular, FolderRegular, FullScreenMaximizeRegular, InfoRegular, NavigationRegular, PhoneLaptopRegular, PhoneRegular, PlayRegular, PlugConnectedRegular, PlugDisconnectedRegular, SaveRegular, StopRegular, TextGrammarErrorRegular, WandRegular, WifiSettingsRegular, WindowConsoleRegular } from '@fluentui/react-icons';
|
||||
|
||||
const STYLE = {};
|
||||
|
||||
export function register() {
|
||||
registerIcons({
|
||||
icons: {
|
||||
AddCircle: <AddCircleRegular style={STYLE} />,
|
||||
ArrowClockwise: <ArrowClockwiseRegular style={STYLE} />,
|
||||
Bookmark: <BookmarkRegular style={STYLE} />,
|
||||
Camera: <CameraRegular style={STYLE} />,
|
||||
ChevronDown: <ChevronDownRegular style={STYLE} />,
|
||||
ChevronRight: <ChevronRightRegular style={STYLE} />,
|
||||
ChevronUp: <ChevronUpRegular style={STYLE} />,
|
||||
Circle: <CircleRegular style={STYLE} />,
|
||||
Copy: <CopyRegular style={STYLE} />,
|
||||
CloudArrowUp: <CloudArrowUpRegular style={STYLE} />,
|
||||
CloudArrowDown: <CloudArrowDownRegular style={STYLE} />,
|
||||
Delete: <DeleteRegular style={STYLE} />,
|
||||
Document: <DocumentRegular style={STYLE} />,
|
||||
Folder: <FolderRegular style={STYLE} />,
|
||||
FullScreenMaximize: <FullScreenMaximizeRegular style={STYLE} />,
|
||||
Info: <InfoRegular style={STYLE} />,
|
||||
Navigation: <NavigationRegular style={STYLE} />,
|
||||
Phone: <PhoneRegular style={STYLE} />,
|
||||
PhoneLaptop: <PhoneLaptopRegular style={STYLE} />,
|
||||
Play: <PlayRegular style={STYLE} />,
|
||||
PlugConnected: <PlugConnectedRegular style={STYLE} />,
|
||||
PlugDisconnected: <PlugDisconnectedRegular style={STYLE} />,
|
||||
Save: <SaveRegular style={STYLE} />,
|
||||
Stop: <StopRegular style={STYLE} />,
|
||||
TextGrammarError: <TextGrammarErrorRegular style={STYLE} />,
|
||||
Wand: <WandRegular style={STYLE} />,
|
||||
WifiSettings: <WifiSettingsRegular style={STYLE} />,
|
||||
WindowConsole: <WindowConsoleRegular style={STYLE} />,
|
||||
|
||||
StatusCircleCheckmark: <CheckmarkRegular style={STYLE} />,
|
||||
ChevronUpSmall: <ChevronUpRegular style={STYLE} />,
|
||||
ChevronDownSmall: <ChevronDownRegular style={STYLE} />,
|
||||
CircleRing: <CircleRegular style={STYLE} />,
|
||||
SortUp: <ArrowSortUpRegular style={STYLE} />,
|
||||
SortDown: <ArrowSortDownRegular style={STYLE} />,
|
||||
|
||||
Document20: <DocumentRegular style={{ fontSize: 20, verticalAlign: 'middle' }} />
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export default {
|
||||
AddCircle: 'AddCircle',
|
||||
ArrowClockwise: 'ArrowClockwise',
|
||||
Bookmark: 'Bookmark',
|
||||
Camera: 'Camera',
|
||||
Copy: 'Copy',
|
||||
Circle: 'Circle',
|
||||
ChevronDown: 'ChevronDown',
|
||||
ChevronRight: 'ChevronRight',
|
||||
ChevronUp: 'ChevronUp',
|
||||
CloudArrowUp: 'CloudArrowUp',
|
||||
CloudArrowDown: 'CloudArrowDown',
|
||||
Delete: 'Delete',
|
||||
Document: 'Document',
|
||||
Folder: 'Folder',
|
||||
FullScreenMaximize: 'FullScreenMaximize',
|
||||
Info: 'Info',
|
||||
Navigation: 'Navigation',
|
||||
Phone: 'Phone',
|
||||
PhoneLaptop: 'PhoneLaptop',
|
||||
Play: 'Play',
|
||||
PlugConnected: 'PlugConnected',
|
||||
PlugDisconnected: 'PlugDisconnected',
|
||||
Save: 'Save',
|
||||
Stop: 'Stop',
|
||||
TextGrammarError: 'TextGrammarError',
|
||||
Wand: 'Wand',
|
||||
WifiSettings: 'WifiSettings',
|
||||
WindowConsole: 'WindowConsole',
|
||||
Document20: 'Document20'
|
||||
};
|
|
@ -1,6 +1,7 @@
|
|||
export * from './async-effect';
|
||||
export * from './file-size';
|
||||
export * from './file';
|
||||
export * from './file-size';
|
||||
export { default as Icons } from './icons';
|
||||
export * from './resize-observer';
|
||||
export * from './with-display-name';
|
||||
export * from './styles';
|
||||
export * from './with-display-name';
|
||||
|
|
136
common/config/rush/pnpm-lock.yaml
generated
136
common/config/rush/pnpm-lock.yaml
generated
|
@ -1,16 +1,19 @@
|
|||
dependencies:
|
||||
'@docusaurus/core': 2.0.0-beta.3_react-dom@17.0.2+react@17.0.2
|
||||
'@docusaurus/preset-classic': 2.0.0-beta.3_12a6012245369fd5be825566be975ff0
|
||||
'@fluentui/font-icons-mdl2': 8.1.14_b094b78811fc8d2f00a90f13d0251fb6
|
||||
'@fluentui/react': 8.36.3_12a6012245369fd5be825566be975ff0
|
||||
'@fluentui/react-file-type-icons': 8.4.3_b094b78811fc8d2f00a90f13d0251fb6
|
||||
'@fluentui/react-hooks': 8.3.4_b094b78811fc8d2f00a90f13d0251fb6
|
||||
'@fluentui/react-icons': 2.0.154-beta.5_d2f1067edc0bfed1f9bdc78013a98cbb
|
||||
'@fluentui/react-make-styles': 9.0.0-beta.4_12a6012245369fd5be825566be975ff0
|
||||
'@mdx-js/loader': 1.6.22_react@17.0.2
|
||||
'@mdx-js/react': 1.6.22_react@17.0.2
|
||||
'@next/mdx': 11.1.2_f56c41adb6190c4680be4a1c0222355d
|
||||
'@rush-temp/adb': file:projects/adb.tgz
|
||||
'@rush-temp/adb-backend-direct-sockets': file:projects/adb-backend-direct-sockets.tgz
|
||||
'@rush-temp/adb-backend-webusb': file:projects/adb-backend-webusb.tgz
|
||||
'@rush-temp/adb-backend-ws': file:projects/adb-backend-ws.tgz
|
||||
'@rush-temp/adb-credential-web': file:projects/adb-credential-web.tgz
|
||||
'@rush-temp/demo': file:projects/demo.tgz_@mdx-js+react@1.6.22
|
||||
'@rush-temp/event': file:projects/event.tgz
|
||||
'@rush-temp/scrcpy': file:projects/scrcpy.tgz
|
||||
|
@ -2079,6 +2082,10 @@ packages:
|
|||
node: '>=12.13.0'
|
||||
resolution:
|
||||
integrity: sha512-DApc6xcb3CvvsBCfRU6Zk3KoZa4mZfCJA4XRv5zhlhaSb0GFuAo7KQ353RUu6d0eYYylY3GGRABXkxRE1SEClA==
|
||||
/@emotion/hash/0.8.0:
|
||||
dev: false
|
||||
resolution:
|
||||
integrity: sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==
|
||||
/@eslint/eslintrc/0.4.3:
|
||||
dependencies:
|
||||
ajv: 6.12.6
|
||||
|
@ -2141,6 +2148,21 @@ packages:
|
|||
dev: false
|
||||
resolution:
|
||||
integrity: sha512-pVY2m3IC5+LLmMzsaPApX9eKTzpOzdgQwrR3FNTE6mGx3N/+QWYM7fdF+T1ldZQt87dCRSeQnmAo5kqjtxeA/w==
|
||||
/@fluentui/keyboard-keys/9.0.0-beta.1:
|
||||
dev: false
|
||||
resolution:
|
||||
integrity: sha512-eGKEzdyIep7KZPoKwHVfcDR3hKuSnI0iDqZGYrIOVWYVr1aD9uZ3JXFK2ad8DZtOGnxgK3/pOvAfqFo61LoRjQ==
|
||||
/@fluentui/make-styles/9.0.0-beta.3:
|
||||
dependencies:
|
||||
'@emotion/hash': 0.8.0
|
||||
csstype: 2.6.19
|
||||
inline-style-expand-shorthand: 1.3.0
|
||||
rtl-css-js: 1.15.0
|
||||
stylis: 4.0.13
|
||||
tslib: 2.3.1
|
||||
dev: false
|
||||
resolution:
|
||||
integrity: sha512-Bwe3BTLC5ATNcQdUk0U4s+eKU7OlV0UF4dlVjnlNYQefMfTWfr/ozZa3IczATOfYKyYPad1KOM3SBUojUdaoog==
|
||||
/@fluentui/merge-styles/8.2.0:
|
||||
dependencies:
|
||||
'@fluentui/set-version': 8.1.4
|
||||
|
@ -2191,6 +2213,69 @@ packages:
|
|||
react: '>=16.8.0 <18.0.0'
|
||||
resolution:
|
||||
integrity: sha512-uWaNalWpgsjDaPki4rXzEJAbdOTy1HH+Kwo2Olfyxfm0geePWBnCjKikXI1xIaS/o47ew2B4TQyvR4ShvudOmA==
|
||||
/@fluentui/react-icons/2.0.154-beta.5_d2f1067edc0bfed1f9bdc78013a98cbb:
|
||||
dependencies:
|
||||
'@fluentui/react-make-styles': 9.0.0-beta.4_12a6012245369fd5be825566be975ff0
|
||||
dev: false
|
||||
peerDependencies:
|
||||
'@fluentui/react-make-styles': ^9.0.0-beta.3
|
||||
resolution:
|
||||
integrity: sha512-U+bdvX1BZELUEN5MK4BX7H35p92Kyt/M+26PWJG/gvc2KCgpfFTFedBXjABKEf1Jo8wIcQl8VWciKSkpzhUaQw==
|
||||
/@fluentui/react-make-styles/9.0.0-beta.4_12a6012245369fd5be825566be975ff0:
|
||||
dependencies:
|
||||
'@fluentui/make-styles': 9.0.0-beta.3
|
||||
'@fluentui/react-shared-contexts': 9.0.0-beta.4_12a6012245369fd5be825566be975ff0
|
||||
'@fluentui/react-theme': 9.0.0-beta.4_12a6012245369fd5be825566be975ff0
|
||||
'@fluentui/react-utilities': 9.0.0-beta.4_b094b78811fc8d2f00a90f13d0251fb6
|
||||
'@types/react': 17.0.27
|
||||
react: 17.0.2
|
||||
tslib: 2.3.1
|
||||
dev: false
|
||||
peerDependencies:
|
||||
'@types/react': '>=16.8.0 <18.0.0'
|
||||
react: '>=16.8.0 <18.0.0'
|
||||
react-dom: '*'
|
||||
resolution:
|
||||
integrity: sha512-z6yy4xKc8+4YgmsCqluTHNwVyCoG5EuQauIOaeT8kQVvV+vtxTSTqqj9nzG9uknGu29zlwqSzqfI4U5WKEFHyA==
|
||||
/@fluentui/react-shared-contexts/9.0.0-beta.4_12a6012245369fd5be825566be975ff0:
|
||||
dependencies:
|
||||
'@fluentui/react-theme': 9.0.0-beta.4_12a6012245369fd5be825566be975ff0
|
||||
'@types/react': 17.0.27
|
||||
react: 17.0.2
|
||||
tslib: 2.3.1
|
||||
dev: false
|
||||
peerDependencies:
|
||||
'@types/react': '>=16.8.0 <18.0.0'
|
||||
react: '>=16.8.0 <18.0.0'
|
||||
react-dom: '*'
|
||||
resolution:
|
||||
integrity: sha512-I4Wb0KHQfDAdWMXb+Af8LvmEvsnzMZ4Oky0ubxNTlueALSdwUE4mVj7dKEasCNESdxR0wXm8YooWh1xCX9WG7w==
|
||||
/@fluentui/react-theme/9.0.0-beta.4_12a6012245369fd5be825566be975ff0:
|
||||
dependencies:
|
||||
'@types/react': 17.0.27
|
||||
react: 17.0.2
|
||||
react-dom: 17.0.2_react@17.0.2
|
||||
tslib: 2.3.1
|
||||
dev: false
|
||||
peerDependencies:
|
||||
'@types/react': '>=16.8.0 <18.0.0'
|
||||
'@types/react-dom': '>=16.8.0 <18.0.0'
|
||||
react: '>=16.8.0 <18.0.0'
|
||||
react-dom: '>=16.8.0 <18.0.0'
|
||||
resolution:
|
||||
integrity: sha512-dfmfZFgb03d43ZguIKbQIGuKEhyYUDNJLheLLV/1CdzZWHxayxEpHofILc6S7L7KVDIhZPniOSjgxmn+R3kzuw==
|
||||
/@fluentui/react-utilities/9.0.0-beta.4_b094b78811fc8d2f00a90f13d0251fb6:
|
||||
dependencies:
|
||||
'@fluentui/keyboard-keys': 9.0.0-beta.1
|
||||
'@types/react': 17.0.27
|
||||
react: 17.0.2
|
||||
tslib: 2.3.1
|
||||
dev: false
|
||||
peerDependencies:
|
||||
'@types/react': '>=16.8.0 <18.0.0'
|
||||
react: '>=16.8.0 <18.0.0'
|
||||
resolution:
|
||||
integrity: sha512-b5drXcG02SNHokC39/B19dt1cti0ErtyS94ymMl1ppZ/Lh02/3VYukYsTBXBpKim4zmALAxqGkx/yXm95A7UBQ==
|
||||
/@fluentui/react-window-provider/2.1.4_b094b78811fc8d2f00a90f13d0251fb6:
|
||||
dependencies:
|
||||
'@fluentui/set-version': 8.1.4
|
||||
|
@ -5301,6 +5386,10 @@ packages:
|
|||
node: '>=8'
|
||||
resolution:
|
||||
integrity: sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==
|
||||
/csstype/2.6.19:
|
||||
dev: false
|
||||
resolution:
|
||||
integrity: sha512-ZVxXaNy28/k3kJg0Fou5MiYpp88j7H9hLZp8PDC3jV0WFjfH5E9xHb56L0W59cPbKbcHXeP4qyT8PrHp8t6LcQ==
|
||||
/csstype/3.0.8:
|
||||
dev: false
|
||||
resolution:
|
||||
|
@ -7822,6 +7911,10 @@ packages:
|
|||
node: '>=10'
|
||||
resolution:
|
||||
integrity: sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==
|
||||
/inline-style-expand-shorthand/1.3.0:
|
||||
dev: false
|
||||
resolution:
|
||||
integrity: sha512-cYW3cf2Tzi43jjHk8yyHAAnwgVXOC0jdmv7QkHMmha2zI2znhWh8LEC+Enb+PHcZi9afsbcP4JHyr5C08jDRHA==
|
||||
/inline-style-parser/0.1.1:
|
||||
dev: false
|
||||
resolution:
|
||||
|
@ -11957,6 +12050,12 @@ packages:
|
|||
node: 6.* || >= 7.*
|
||||
resolution:
|
||||
integrity: sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==
|
||||
/rtl-css-js/1.15.0:
|
||||
dependencies:
|
||||
'@babel/runtime': 7.16.3
|
||||
dev: false
|
||||
resolution:
|
||||
integrity: sha512-99Cu4wNNIhrI10xxUaABHsdDqzalrSRTie4GeCmbGVuehm4oj+fIy8fTzB+16pmKe8Bv9rl+hxIBez6KxExTew==
|
||||
/rtl-detect/1.0.3:
|
||||
dev: false
|
||||
resolution:
|
||||
|
@ -12856,6 +12955,10 @@ packages:
|
|||
dev: false
|
||||
resolution:
|
||||
integrity: sha512-8/3pSmthWM7lsPBKv7NXkzn2Uc9W7NotcwGNpJaa3k7WMM1XDCA4MgT5k/8BIexd5ydZdboXtU90XH9Ec4Bv/Q==
|
||||
/stylis/4.0.13:
|
||||
dev: false
|
||||
resolution:
|
||||
integrity: sha512-xGPXiFVl4YED9Jh7Euv2V220mriG9u4B2TA6Ybjc1catrstKD2PpIdU3U0RKpkVBC2EhmL/F0sPCr9vrFTNRag==
|
||||
/supports-color/5.5.0:
|
||||
dependencies:
|
||||
has-flag: 3.0.0
|
||||
|
@ -14326,6 +14429,17 @@ packages:
|
|||
dev: false
|
||||
resolution:
|
||||
integrity: sha512-V50KMwwzqJV0NpZIZFwfOD5/lyny3WlSzRiXgA0G7VUnRlqttta1L6UQIHzd6EuBY/cHGfwTIck7w1yH6Q5zUw==
|
||||
file:projects/adb-backend-direct-sockets.tgz:
|
||||
dependencies:
|
||||
'@yume-chan/async': 2.1.4
|
||||
tslib: 2.3.1
|
||||
typescript: 4.4.3
|
||||
dev: false
|
||||
name: '@rush-temp/adb-backend-direct-sockets'
|
||||
resolution:
|
||||
integrity: sha512-To21ySnzlUUAcBspOH3tqG4KH7e8evt+K2/D3kYZOCdgQ9P/y9jGHjm/loWzFj82/9h2OGIzM9rYF9gege8nkQ==
|
||||
tarball: file:projects/adb-backend-direct-sockets.tgz
|
||||
version: 0.0.0
|
||||
file:projects/adb-backend-webusb.tgz:
|
||||
dependencies:
|
||||
'@types/w3c-web-usb': 1.0.5
|
||||
|
@ -14349,6 +14463,17 @@ packages:
|
|||
integrity: sha512-gmOeTpe51r+uwYeeMuPIQ6LU2+DYXJbirNXafLgfLjpWWWH8sCblZADZlHLzI/wte6RfabrecbyTgxoTtJ6rvA==
|
||||
tarball: file:projects/adb-backend-ws.tgz
|
||||
version: 0.0.0
|
||||
file:projects/adb-credential-web.tgz:
|
||||
dependencies:
|
||||
'@yume-chan/async': 2.1.4
|
||||
tslib: 2.3.1
|
||||
typescript: 4.4.3
|
||||
dev: false
|
||||
name: '@rush-temp/adb-credential-web'
|
||||
resolution:
|
||||
integrity: sha512-X7CGrXjmZMJZ1eodb8OPi7Gpf4Td6E82tVGmElOGyHGZQfQUAbquFZEA90Xy3fMkOEL044erE0z2K50FT1XgIA==
|
||||
tarball: file:projects/adb-credential-web.tgz
|
||||
version: 0.0.0
|
||||
file:projects/adb.tgz:
|
||||
dependencies:
|
||||
'@yume-chan/async': 2.1.4
|
||||
|
@ -14367,6 +14492,8 @@ packages:
|
|||
'@fluentui/react': 8.36.3_12a6012245369fd5be825566be975ff0
|
||||
'@fluentui/react-file-type-icons': 8.4.3_b094b78811fc8d2f00a90f13d0251fb6
|
||||
'@fluentui/react-hooks': 8.3.4_b094b78811fc8d2f00a90f13d0251fb6
|
||||
'@fluentui/react-icons': 2.0.154-beta.5_d2f1067edc0bfed1f9bdc78013a98cbb
|
||||
'@fluentui/react-make-styles': 9.0.0-beta.4_12a6012245369fd5be825566be975ff0
|
||||
'@mdx-js/loader': 1.6.22_react@17.0.2
|
||||
'@next/mdx': 11.1.2_f56c41adb6190c4680be4a1c0222355d
|
||||
'@types/dom-webcodecs': 0.1.2
|
||||
|
@ -14398,7 +14525,7 @@ packages:
|
|||
peerDependencies:
|
||||
'@mdx-js/react': '*'
|
||||
resolution:
|
||||
integrity: sha512-tq3k2RWwrKsKzVKXGjy+/z9ROfpdF6OMYdFKonBDgfuOe9HUqke5EUlO54BVvLO68v+X8UvVu3o+5X/RW16scg==
|
||||
integrity: sha512-cvSHC3mz5ixtrLSqs8IsDxT8pMx30CevOxdniisAx4zRBJPjmFNN2CH6jNY0ir5xy18pDM0NhJGKj+EGIvbWWQ==
|
||||
tarball: file:projects/demo.tgz
|
||||
version: 0.0.0
|
||||
file:projects/event.tgz:
|
||||
|
@ -14484,16 +14611,19 @@ packages:
|
|||
specifiers:
|
||||
'@docusaurus/core': ^2.0.0-beta.0
|
||||
'@docusaurus/preset-classic': ^2.0.0-beta.0
|
||||
'@fluentui/font-icons-mdl2': ^8.1.14
|
||||
'@fluentui/react': ^8.36.3
|
||||
'@fluentui/react-file-type-icons': ^8.4.3
|
||||
'@fluentui/react-hooks': ^8.3.4
|
||||
'@fluentui/react-icons': ^2.0.154-beta.5
|
||||
'@fluentui/react-make-styles': ^9.0.0-beta.3
|
||||
'@mdx-js/loader': ^1.6.22
|
||||
'@mdx-js/react': ^1.6.21
|
||||
'@next/mdx': ^11.1.2
|
||||
'@rush-temp/adb': file:./projects/adb.tgz
|
||||
'@rush-temp/adb-backend-direct-sockets': file:./projects/adb-backend-direct-sockets.tgz
|
||||
'@rush-temp/adb-backend-webusb': file:./projects/adb-backend-webusb.tgz
|
||||
'@rush-temp/adb-backend-ws': file:./projects/adb-backend-ws.tgz
|
||||
'@rush-temp/adb-credential-web': file:./projects/adb-credential-web.tgz
|
||||
'@rush-temp/demo': file:./projects/demo.tgz
|
||||
'@rush-temp/event': file:./projects/event.tgz
|
||||
'@rush-temp/scrcpy': file:./projects/scrcpy.tgz
|
||||
|
|
14
libraries/adb-backend-direct-sockets/.npmignore
Normal file
14
libraries/adb-backend-direct-sockets/.npmignore
Normal file
|
@ -0,0 +1,14 @@
|
|||
.rush
|
||||
|
||||
# Test
|
||||
coverage
|
||||
**/*.spec.ts
|
||||
**/*.spec.js
|
||||
**/*.spec.js.map
|
||||
**/__helpers__
|
||||
jest.config.js
|
||||
|
||||
tsconfig.json
|
||||
|
||||
# Logs
|
||||
*.log
|
3
libraries/adb-backend-direct-sockets/README.md
Normal file
3
libraries/adb-backend-direct-sockets/README.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# `@yume-chan/adb-backend-direct-sockets`
|
||||
|
||||
Use Direct Sockets API for plugin-free ADB over WiFi connection.
|
36
libraries/adb-backend-direct-sockets/package.json
Normal file
36
libraries/adb-backend-direct-sockets/package.json
Normal file
|
@ -0,0 +1,36 @@
|
|||
{
|
||||
"name": "@yume-chan/adb-backend-direct-sockets",
|
||||
"private": true,
|
||||
"version": "0.0.9",
|
||||
"description": "Backend for `@yume-chan/adb` using Direct Sockets API.",
|
||||
"keywords": [
|
||||
"adb"
|
||||
],
|
||||
"author": "Simon Chan <cnsimonchan@live.com>",
|
||||
"homepage": "https://github.com/yume-chan/ya-webadb#readme",
|
||||
"license": "MIT",
|
||||
"main": "cjs/index.js",
|
||||
"module": "esm/index.js",
|
||||
"types": "dts/index.d.ts",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/yume-chan/ya-webadb.git"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "build-ts-package",
|
||||
"build:watch": "build-ts-package --incremental"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/yume-chan/ya-webadb/issues"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^4.4.3",
|
||||
"@yume-chan/ts-package-builder": "^1.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@yume-chan/adb": "^0.0.9",
|
||||
"@yume-chan/async": "^2.1.4",
|
||||
"@yume-chan/event": "^0.0.9",
|
||||
"tslib": "^2.3.1"
|
||||
}
|
||||
}
|
111
libraries/adb-backend-direct-sockets/src/index.ts
Normal file
111
libraries/adb-backend-direct-sockets/src/index.ts
Normal file
|
@ -0,0 +1,111 @@
|
|||
import { AdbBackend } from '@yume-chan/adb';
|
||||
import { EventEmitter } from '@yume-chan/event';
|
||||
|
||||
const Utf8Encoder = new TextEncoder();
|
||||
const Utf8Decoder = new TextDecoder();
|
||||
|
||||
declare global {
|
||||
interface TCPSocket {
|
||||
close(): Promise<void>;
|
||||
|
||||
readonly remoteAddress: string;
|
||||
readonly remotePort: number;
|
||||
readonly readable: ReadableStream;
|
||||
readonly writable: WritableStream;
|
||||
}
|
||||
|
||||
interface SocketOptions {
|
||||
localAddress?: string | undefined;
|
||||
localPort?: number | undefined;
|
||||
|
||||
remoteAddress: string;
|
||||
remotePort: number;
|
||||
|
||||
sendBufferSize?: number;
|
||||
receiveBufferSize?: number;
|
||||
|
||||
keepAlive?: number;
|
||||
noDelay?: boolean;
|
||||
}
|
||||
|
||||
interface Navigator {
|
||||
openTCPSocket(options?: SocketOptions): Promise<TCPSocket>;
|
||||
}
|
||||
}
|
||||
|
||||
export function encodeUtf8(input: string): ArrayBuffer {
|
||||
return Utf8Encoder.encode(input).buffer;
|
||||
}
|
||||
|
||||
export function decodeUtf8(buffer: ArrayBuffer): string {
|
||||
return Utf8Decoder.decode(buffer);
|
||||
}
|
||||
|
||||
export default class AdbDirectSocketsBackend implements AdbBackend {
|
||||
public static isSupported(): boolean {
|
||||
return typeof window !== 'undefined' && !!window.navigator?.openTCPSocket;
|
||||
}
|
||||
|
||||
public readonly serial: string;
|
||||
|
||||
public readonly address: string;
|
||||
|
||||
public readonly port: number;
|
||||
|
||||
public name: string | undefined;
|
||||
|
||||
private socket: TCPSocket | undefined;
|
||||
private reader: ReadableStreamDefaultReader<ArrayBuffer> | undefined;
|
||||
private writer: WritableStreamDefaultWriter<ArrayBuffer> | undefined;
|
||||
|
||||
private _connected = false;
|
||||
public get connected() { return this._connected; }
|
||||
|
||||
private readonly disconnectEvent = new EventEmitter<void>();
|
||||
public readonly onDisconnected = this.disconnectEvent.event;
|
||||
|
||||
public constructor(address: string, port: number = 5555, name?: string) {
|
||||
this.address = address;
|
||||
this.port = port;
|
||||
this.serial = `${address}:${port}`;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public async connect() {
|
||||
const socket = await navigator.openTCPSocket({
|
||||
remoteAddress: this.address,
|
||||
remotePort: this.port,
|
||||
noDelay: true,
|
||||
});
|
||||
|
||||
this.socket = socket;
|
||||
this.reader = this.socket.readable.getReader();
|
||||
this.writer = this.socket.writable.getWriter();
|
||||
|
||||
this._connected = true;
|
||||
}
|
||||
|
||||
public encodeUtf8(input: string): ArrayBuffer {
|
||||
return encodeUtf8(input);
|
||||
}
|
||||
|
||||
public decodeUtf8(buffer: ArrayBuffer): string {
|
||||
return decodeUtf8(buffer);
|
||||
}
|
||||
|
||||
public write(buffer: ArrayBuffer): Promise<void> {
|
||||
return this.writer!.write(buffer);
|
||||
}
|
||||
|
||||
public async read(length: number): Promise<ArrayBuffer> {
|
||||
const result = await this.reader!.read();
|
||||
if (result.value) {
|
||||
return result.value;
|
||||
}
|
||||
throw new Error('Stream ended');
|
||||
}
|
||||
|
||||
public dispose(): void | Promise<void> {
|
||||
this.socket?.close();
|
||||
}
|
||||
}
|
14
libraries/adb-backend-direct-sockets/tsconfig.json
Normal file
14
libraries/adb-backend-direct-sockets/tsconfig.json
Normal file
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"extends": "./node_modules/@yume-chan/ts-package-builder/tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"lib": [
|
||||
"ESNext",
|
||||
"DOM"
|
||||
],
|
||||
},
|
||||
"references": [
|
||||
{
|
||||
"path": "../adb/tsconfig.json"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,4 +1,3 @@
|
|||
export * from './auth';
|
||||
export * from './backend';
|
||||
export { AdbWebUsbBackend as default } from './backend';
|
||||
export * from './utils';
|
||||
|
|
3
libraries/adb-credential-web/README.md
Normal file
3
libraries/adb-credential-web/README.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# `@yume-chan/adb-credential-web`
|
||||
|
||||
Store ADB credentials in LocalStorage.
|
36
libraries/adb-credential-web/package.json
Normal file
36
libraries/adb-credential-web/package.json
Normal file
|
@ -0,0 +1,36 @@
|
|||
{
|
||||
"name": "@yume-chan/adb-credential-web",
|
||||
"private": true,
|
||||
"version": "0.0.9",
|
||||
"description": "Credential Store for `@yume-chan/adb` using Web LocalStorage API.",
|
||||
"keywords": [
|
||||
"adb"
|
||||
],
|
||||
"author": "Simon Chan <cnsimonchan@live.com>",
|
||||
"homepage": "https://github.com/yume-chan/ya-webadb#readme",
|
||||
"license": "MIT",
|
||||
"main": "cjs/index.js",
|
||||
"module": "esm/index.js",
|
||||
"types": "dts/index.d.ts",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/yume-chan/ya-webadb.git"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "build-ts-package",
|
||||
"build:watch": "build-ts-package --incremental"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/yume-chan/ya-webadb/issues"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^4.4.3",
|
||||
"@yume-chan/ts-package-builder": "^1.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@yume-chan/adb": "^0.0.9",
|
||||
"@yume-chan/async": "^2.1.4",
|
||||
"@yume-chan/event": "^0.0.9",
|
||||
"tslib": "^2.3.1"
|
||||
}
|
||||
}
|
|
@ -1,7 +1,17 @@
|
|||
import { AdbCredentialStore, calculateBase64EncodedLength, calculatePublicKey, calculatePublicKeyLength, decodeBase64, encodeBase64 } from "@yume-chan/adb";
|
||||
import { decodeUtf8 } from "./utils";
|
||||
|
||||
export class AdbWebCredentialStore implements AdbCredentialStore {
|
||||
const Utf8Encoder = new TextEncoder();
|
||||
const Utf8Decoder = new TextDecoder();
|
||||
|
||||
export function encodeUtf8(input: string): ArrayBuffer {
|
||||
return Utf8Encoder.encode(input).buffer;
|
||||
}
|
||||
|
||||
export function decodeUtf8(buffer: ArrayBuffer): string {
|
||||
return Utf8Decoder.decode(buffer);
|
||||
}
|
||||
|
||||
export default class AdbWebCredentialStore implements AdbCredentialStore {
|
||||
public readonly localStorageKey: string;
|
||||
|
||||
public constructor(localStorageKey = 'private-key') {
|
||||
|
@ -44,5 +54,4 @@ export class AdbWebCredentialStore implements AdbCredentialStore {
|
|||
|
||||
return privateKey;
|
||||
}
|
||||
|
||||
}
|
14
libraries/adb-credential-web/tsconfig.json
Normal file
14
libraries/adb-credential-web/tsconfig.json
Normal file
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"extends": "./node_modules/@yume-chan/ts-package-builder/tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"lib": [
|
||||
"ESNext",
|
||||
"DOM"
|
||||
],
|
||||
},
|
||||
"references": [
|
||||
{
|
||||
"path": "../adb/tsconfig.json"
|
||||
}
|
||||
]
|
||||
}
|
37
rush.json
37
rush.json
|
@ -4,7 +4,6 @@
|
|||
*/
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/rush/v5/rush.schema.json",
|
||||
|
||||
/**
|
||||
* (Required) This specifies the version of the Rush engine to be used in this repo.
|
||||
* Rush's "version selector" feature ensures that the globally installed tool will
|
||||
|
@ -17,7 +16,6 @@
|
|||
* correct error-underlining and tab-completion for editors such as VS Code.
|
||||
*/
|
||||
"rushVersion": "5.55.1",
|
||||
|
||||
/**
|
||||
* The next field selects which package manager should be installed and determines its version.
|
||||
* Rush installs its own local copy of the package manager to ensure that your build process
|
||||
|
@ -27,10 +25,8 @@
|
|||
* for details about these alternatives.
|
||||
*/
|
||||
"pnpmVersion": "5.15.2",
|
||||
|
||||
// "npmVersion": "4.5.0",
|
||||
// "yarnVersion": "1.9.4",
|
||||
|
||||
/**
|
||||
* Options that are only used when the PNPM package manager is selected
|
||||
*/
|
||||
|
@ -51,7 +47,6 @@
|
|||
* The default value is "local".
|
||||
*/
|
||||
// "pnpmStore": "local",
|
||||
|
||||
/**
|
||||
* If true, then Rush will add the "--strict-peer-dependencies" option when invoking PNPM.
|
||||
* This causes "rush install" to fail if there are unsatisfied peer dependencies, which is
|
||||
|
@ -63,7 +58,6 @@
|
|||
* It is strongly recommended to set strictPeerDependencies=true.
|
||||
*/
|
||||
// "strictPeerDependencies": true,
|
||||
|
||||
/**
|
||||
* Configures the strategy used to select versions during installation.
|
||||
*
|
||||
|
@ -77,7 +71,6 @@
|
|||
* will recalculate all version selections.
|
||||
*/
|
||||
// "resolutionStrategy": "fast",
|
||||
|
||||
/**
|
||||
* If true, then `rush install` will report an error if manual modifications
|
||||
* were made to the PNPM shrinkwrap file without running "rush update" afterwards.
|
||||
|
@ -95,7 +88,6 @@
|
|||
* The default value is false.
|
||||
*/
|
||||
// "preventManualShrinkwrapChanges": true,
|
||||
|
||||
/**
|
||||
* If true, then `rush install` will use the PNPM workspaces feature to perform the
|
||||
* install.
|
||||
|
@ -112,7 +104,6 @@
|
|||
*/
|
||||
// "useWorkspaces": true
|
||||
},
|
||||
|
||||
/**
|
||||
* Older releases of the Node.js engine may be missing features required by your system.
|
||||
* Other releases may have bugs. In particular, the "latest" version will not be a
|
||||
|
@ -125,7 +116,6 @@
|
|||
* LTS versions: https://nodejs.org/en/download/releases/
|
||||
*/
|
||||
"nodeSupportedVersionRange": ">=12.13.0 <13.0.0 || >=14.15.0 <15.0.0",
|
||||
|
||||
/**
|
||||
* Odd-numbered major versions of Node.js are experimental. Even-numbered releases
|
||||
* spend six months in a stabilization period before the first Long Term Support (LTS) version.
|
||||
|
@ -138,7 +128,6 @@
|
|||
* to disable Rush's warning.
|
||||
*/
|
||||
// "suppressNodeLtsWarning": false,
|
||||
|
||||
/**
|
||||
* If you would like the version specifiers for your dependencies to be consistent, then
|
||||
* uncomment this line. This is effectively similar to running "rush check" before any
|
||||
|
@ -151,7 +140,6 @@
|
|||
* section of the common-versions.json.
|
||||
*/
|
||||
"ensureConsistentVersions": true,
|
||||
|
||||
/**
|
||||
* Large monorepos can become intimidating for newcomers if project folder paths don't follow
|
||||
* a consistent and recognizable pattern. When the system allows nested folder trees,
|
||||
|
@ -177,7 +165,6 @@
|
|||
*/
|
||||
// "projectFolderMinDepth": 2,
|
||||
// "projectFolderMaxDepth": 2,
|
||||
|
||||
/**
|
||||
* Today the npmjs.com registry enforces fairly strict naming rules for packages, but in the early
|
||||
* days there was no standard and hardly any enforcement. A few large legacy projects are still using
|
||||
|
@ -190,7 +177,6 @@
|
|||
* The default value is false.
|
||||
*/
|
||||
// "allowMostlyStandardPackageNames": true,
|
||||
|
||||
/**
|
||||
* This feature helps you to review and approve new packages before they are introduced
|
||||
* to your monorepo. For example, you may be concerned about licensing, code quality,
|
||||
|
@ -226,7 +212,6 @@
|
|||
// */
|
||||
// // "ignoredNpmScopes": ["@types"]
|
||||
// },
|
||||
|
||||
/**
|
||||
* If you use Git as your version control system, this section has some additional
|
||||
* optional features you can use.
|
||||
|
@ -247,14 +232,12 @@
|
|||
// "[^@]+@users\\.noreply\\.github\\.com",
|
||||
// "travis@example\\.org"
|
||||
// ],
|
||||
|
||||
/**
|
||||
* When Rush reports that the address is malformed, the notice can include an example
|
||||
* of a recommended email. Make sure it conforms to one of the allowedEmailRegExps
|
||||
* expressions.
|
||||
*/
|
||||
// "sampleEmail": "mrexample@users.noreply.github.com",
|
||||
|
||||
/**
|
||||
* The commit message to use when committing changes during 'rush publish'.
|
||||
*
|
||||
|
@ -263,7 +246,6 @@
|
|||
* in the commit message, and then customize Rush's message to contain that string.
|
||||
*/
|
||||
// "versionBumpCommitMessage": "Applying package updates. [skip-ci]",
|
||||
|
||||
/**
|
||||
* The commit message to use when committing changes during 'rush version'.
|
||||
*
|
||||
|
@ -273,7 +255,6 @@
|
|||
*/
|
||||
// "changeLogUpdateCommitMessage": "Deleting change files and updating change logs for package updates. [skip-ci]"
|
||||
},
|
||||
|
||||
"repository": {
|
||||
/**
|
||||
* The URL of this Git repository, used by "rush change" to determine the base branch for your PR.
|
||||
|
@ -291,20 +272,17 @@
|
|||
* to retrieve the latest activity for the remote master branch.
|
||||
*/
|
||||
"url": "https://github.com/yume-chan/ya-webadb"
|
||||
|
||||
/**
|
||||
* The default branch name. This tells "rush change" which remote branch to compare against.
|
||||
* The default value is "master"
|
||||
*/
|
||||
// "defaultBranch": "master",
|
||||
|
||||
/**
|
||||
* The default remote. This tells "rush change" which remote to compare against if the remote URL is
|
||||
* not set or if a remote matching the provided remote URL is not found.
|
||||
*/
|
||||
// "defaultRemote": "origin"
|
||||
},
|
||||
|
||||
/**
|
||||
* Event hooks are customized script actions that Rush executes when specific events occur
|
||||
*/
|
||||
|
@ -315,25 +293,21 @@
|
|||
"preRushInstall": [
|
||||
// "common/scripts/pre-rush-install.js"
|
||||
],
|
||||
|
||||
/**
|
||||
* The list of shell commands to run after the Rush installation finishes
|
||||
*/
|
||||
"postRushInstall": [
|
||||
"rush postinstall"
|
||||
],
|
||||
|
||||
/**
|
||||
* The list of shell commands to run before the Rush build command starts
|
||||
*/
|
||||
"preRushBuild": [],
|
||||
|
||||
/**
|
||||
* The list of shell commands to run after the Rush build command finishes
|
||||
*/
|
||||
"postRushBuild": []
|
||||
},
|
||||
|
||||
/**
|
||||
* Installation variants allow you to maintain a parallel set of configuration files that can be
|
||||
* used to build the entire monorepo with an alternate set of dependencies. For example, suppose
|
||||
|
@ -364,7 +338,6 @@
|
|||
// "description": "Build this repo using the previous release of the SDK"
|
||||
// }
|
||||
],
|
||||
|
||||
/**
|
||||
* Rush can collect anonymous telemetry about everyday developer activity such as
|
||||
* success/failure of installs, builds, and other operations. You can use this to identify
|
||||
|
@ -374,14 +347,12 @@
|
|||
* in the "eventHooks" section.
|
||||
*/
|
||||
// "telemetryEnabled": false,
|
||||
|
||||
/**
|
||||
* Allows creation of hotfix changes. This feature is experimental so it is disabled by default.
|
||||
* If this is set, 'rush change' only allows a 'hotfix' change type to be specified. This change type
|
||||
* will be used when publishing subsequent changes from the monorepo.
|
||||
*/
|
||||
// "hotfixChangeEnabled": false,
|
||||
|
||||
/**
|
||||
* (Required) This is the inventory of projects to be managed by Rush.
|
||||
*
|
||||
|
@ -483,6 +454,14 @@
|
|||
"packageName": "@yume-chan/adb-backend-ws",
|
||||
"projectFolder": "libraries/adb-backend-ws"
|
||||
},
|
||||
{
|
||||
"packageName": "@yume-chan/adb-backend-direct-sockets",
|
||||
"projectFolder": "libraries/adb-backend-direct-sockets"
|
||||
},
|
||||
{
|
||||
"packageName": "@yume-chan/adb-credential-web",
|
||||
"projectFolder": "libraries/adb-credential-web"
|
||||
},
|
||||
{
|
||||
"packageName": "@yume-chan/scrcpy",
|
||||
"projectFolder": "libraries/scrcpy"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue