feat(adb): add power related api

This commit is contained in:
Simon Chan 2022-01-13 17:28:07 +08:00
parent 1ea248d57e
commit 45d784c8a5
19 changed files with 196 additions and 106 deletions

View file

@ -6,7 +6,7 @@ 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 { globalState, logger } from '../state';
import { CommonStackTokens, Icons } from '../utils';
const DropdownStyles = { dropdown: { width: '100%' } };
@ -33,7 +33,7 @@ function _Connect(): JSX.Element | null {
setSupported(supported);
if (!supported) {
global.showErrorDialog('Your browser does not support WebUSB standard, which is required for this site to work.\n\nLatest version of Google Chrome, Microsoft Edge, or other Chromium-based browsers are required.');
globalState.showErrorDialog('Your browser does not support WebUSB standard, which is required for this site to work.\n\nLatest version of Google Chrome, Microsoft Edge, or other Chromium-based browsers are required.');
return;
}
@ -134,24 +134,24 @@ function _Connect(): JSX.Element | null {
try {
setConnecting(true);
await device.connect(CredentialStore);
global.setDevice(device);
globalState.setDevice(device);
} catch (e) {
device.dispose();
throw e;
}
}
} catch (e: any) {
global.showErrorDialog(e.message);
globalState.showErrorDialog(e.message);
} finally {
setConnecting(false);
}
}, [selectedBackend]);
const disconnect = useCallback(async () => {
try {
await global.device!.dispose();
global.setDevice(undefined);
await globalState.device!.dispose();
globalState.setDevice(undefined);
} catch (e: any) {
global.showErrorDialog(e.message);
globalState.showErrorDialog(e.message);
}
}, []);
@ -214,7 +214,7 @@ function _Connect(): JSX.Element | null {
tokens={{ childrenGap: 8, padding: '0 0 8px 8px' }}
>
<Dropdown
disabled={!!global.device || backendOptions.length === 0}
disabled={!!globalState.device || backendOptions.length === 0}
label="Available devices"
placeholder="No available devices"
options={backendOptions}
@ -224,7 +224,7 @@ function _Connect(): JSX.Element | null {
onChange={handleSelectedBackendChange}
/>
{!global.device
{!globalState.device
? (
<Stack horizontal tokens={CommonStackTokens}>
<StackItem grow shrink>

View file

@ -3,7 +3,7 @@ import { AdbDemoModeMobileDataType, AdbDemoModeMobileDataTypes, AdbDemoModeSigna
import { autorun, makeAutoObservable, reaction, runInAction } from "mobx";
import { observer } from "mobx-react-lite";
import { CSSProperties, useCallback } from 'react';
import { global } from "../state";
import { globalState } from "../state";
const SignalStrengthOptions =
Object.values(AdbDemoModeSignalStrength)
@ -65,7 +65,7 @@ class DemoModeState {
makeAutoObservable(this);
reaction(
() => global.device,
() => globalState.device,
async (device) => {
if (device) {
const allowed = await device.demoMode.getAllowed();
@ -120,21 +120,21 @@ const FEATURES: FeatureDefinition[][] = [
max: 100,
step: 1,
initial: 100,
onChange: (value) => global.device!.demoMode.setBatteryLevel(value as number),
onChange: (value) => globalState.device!.demoMode.setBatteryLevel(value as number),
},
{
key: 'batteryCharging',
label: 'Battery Charging',
type: 'boolean',
initial: false,
onChange: (value) => global.device!.demoMode.setBatteryCharging(value as boolean),
onChange: (value) => globalState.device!.demoMode.setBatteryCharging(value as boolean),
},
{
key: 'powerSaveMode',
label: 'Power Save Mode',
type: 'boolean',
initial: false,
onChange: (value) => global.device!.demoMode.setPowerSaveMode(value as boolean),
onChange: (value) => globalState.device!.demoMode.setPowerSaveMode(value as boolean),
},
],
[
@ -144,14 +144,14 @@ const FEATURES: FeatureDefinition[][] = [
type: 'select',
options: SignalStrengthOptions,
initial: AdbDemoModeSignalStrength.Level4,
onChange: (value) => global.device!.demoMode.setWifiSignalStrength(value as AdbDemoModeSignalStrength),
onChange: (value) => globalState.device!.demoMode.setWifiSignalStrength(value as AdbDemoModeSignalStrength),
},
{
key: 'airplaneMode',
label: 'Airplane Mode',
type: 'boolean',
initial: false,
onChange: (value) => global.device!.demoMode.setAirplaneMode(value as boolean),
onChange: (value) => globalState.device!.demoMode.setAirplaneMode(value as boolean),
},
{
key: 'mobileDataType',
@ -159,7 +159,7 @@ const FEATURES: FeatureDefinition[][] = [
type: 'select',
options: MobileDataTypeOptions,
initial: 'lte',
onChange: (value) => global.device!.demoMode.setMobileDataType(value as AdbDemoModeMobileDataType),
onChange: (value) => globalState.device!.demoMode.setMobileDataType(value as AdbDemoModeMobileDataType),
},
{
key: 'mobileSignalStrength',
@ -167,7 +167,7 @@ const FEATURES: FeatureDefinition[][] = [
type: 'select',
options: SignalStrengthOptions,
initial: AdbDemoModeSignalStrength.Level4,
onChange: (value) => global.device!.demoMode.setMobileSignalStrength(value as AdbDemoModeSignalStrength),
onChange: (value) => globalState.device!.demoMode.setMobileSignalStrength(value as AdbDemoModeSignalStrength),
},
],
[
@ -177,42 +177,42 @@ const FEATURES: FeatureDefinition[][] = [
type: 'select',
options: StatusBarModeOptions,
initial: 'transparent',
onChange: (value) => global.device!.demoMode.setStatusBarMode(value as AdbDemoModeStatusBarMode),
onChange: (value) => globalState.device!.demoMode.setStatusBarMode(value as AdbDemoModeStatusBarMode),
},
{
key: 'vibrateMode',
label: 'Vibrate Mode Indicator',
type: 'boolean',
initial: false,
onChange: (value) => global.device!.demoMode.setVibrateModeEnabled(value as boolean),
onChange: (value) => globalState.device!.demoMode.setVibrateModeEnabled(value as boolean),
},
{
key: 'bluetoothConnected',
label: 'Bluetooth Indicator',
type: 'boolean',
initial: false,
onChange: (value) => global.device!.demoMode.setBluetoothConnected(value as boolean),
onChange: (value) => globalState.device!.demoMode.setBluetoothConnected(value as boolean),
},
{
key: 'locatingIcon',
label: 'Locating Icon',
type: 'boolean',
initial: false,
onChange: (value) => global.device!.demoMode.setLocatingIcon(value as boolean),
onChange: (value) => globalState.device!.demoMode.setLocatingIcon(value as boolean),
},
{
key: 'alarmIcon',
label: 'Alarm Icon',
type: 'boolean',
initial: false,
onChange: (value) => global.device!.demoMode.setAlarmIcon(value as boolean),
onChange: (value) => globalState.device!.demoMode.setAlarmIcon(value as boolean),
},
{
key: 'notificationsVisibility',
label: 'Notifications Visibility',
type: 'boolean',
initial: true,
onChange: (value) => global.device!.demoMode.setNotificationsVisibility(value as boolean),
onChange: (value) => globalState.device!.demoMode.setNotificationsVisibility(value as boolean),
},
{
key: 'hour',
@ -222,7 +222,7 @@ const FEATURES: FeatureDefinition[][] = [
max: 23,
step: 1,
initial: 12,
onChange: (value) => global.device!.demoMode.setTime(value as number, state.features.get('minute') as number | undefined ?? 34)
onChange: (value) => globalState.device!.demoMode.setTime(value as number, state.features.get('minute') as number | undefined ?? 34)
},
{
key: 'minute',
@ -232,7 +232,7 @@ const FEATURES: FeatureDefinition[][] = [
max: 59,
step: 1,
initial: 34,
onChange: (value) => global.device!.demoMode.setTime(state.features.get('hour') as number | undefined ?? 34, value as number)
onChange: (value) => globalState.device!.demoMode.setTime(state.features.get('hour') as number | undefined ?? 34, value as number)
},
],
];
@ -306,7 +306,7 @@ const DemoModeBase = ({
style,
}: DemoModeProps) => {
const handleAllowedChange = useCallback(async (e, value?: boolean) => {
await global.device!.demoMode.setAllowed(value!);
await globalState.device!.demoMode.setAllowed(value!);
runInAction(() => {
state.allowed = value!;
state.enabled = false;
@ -314,7 +314,7 @@ const DemoModeBase = ({
}, []);
const handleEnabledChange = useCallback(async (e, value?: boolean) => {
await global.device!.demoMode.setEnabled(value!);
await globalState.device!.demoMode.setEnabled(value!);
runInAction(() => state.enabled = value!);
}, []);
@ -322,7 +322,7 @@ const DemoModeBase = ({
<div style={{ padding: 12, overflow: 'hidden auto', ...style }}>
<Toggle
label="Allowed"
disabled={!global.device}
disabled={!globalState.device}
checked={state.allowed}
onChange={handleAllowedChange}
/>

View file

@ -1,7 +1,7 @@
import { Dialog, DialogFooter, DialogType, PrimaryButton } from '@fluentui/react';
import { observer } from "mobx-react-lite";
import { PropsWithChildren } from 'react';
import { global } from '../state';
import { globalState } from '../state';
export const ErrorDialogProvider = observer((props: PropsWithChildren<{}>) => {
return (
@ -9,15 +9,15 @@ export const ErrorDialogProvider = observer((props: PropsWithChildren<{}>) => {
{props.children}
<Dialog
hidden={!global.errorDialogVisible}
hidden={!globalState.errorDialogVisible}
dialogContentProps={{
type: DialogType.normal,
title: 'Error',
subText: global.errorDialogMessage,
subText: globalState.errorDialogMessage,
}}
>
<DialogFooter>
<PrimaryButton text="OK" onClick={global.hideErrorDialog} />
<PrimaryButton text="OK" onClick={globalState.hideErrorDialog} />
</DialogFooter>
</Dialog>
</>

View file

@ -3,7 +3,7 @@ import { AdbPacketInit, decodeUtf8 } from '@yume-chan/adb';
import { DisposableList } from '@yume-chan/event';
import { observer } from "mobx-react-lite";
import { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
import { global, logger } from "../state";
import { globalState, logger } from "../state";
import { Icons, withDisplayName } from '../utils';
import { CommandBar } from './command-bar';
@ -57,10 +57,10 @@ const LogLine = withDisplayName('LoggerLine')(({ packet }: { packet: [string, Ad
export const ToggleLogView = observer(() => {
return (
<IconButton
checked={global.logVisible}
checked={globalState.logVisible}
iconProps={{ iconName: Icons.TextGrammarError }}
title="Toggle Log"
onClick={global.toggleLog}
onClick={globalState.toggleLog}
/>
);
});
@ -142,7 +142,7 @@ export const LogView = observer(({
classNames['logger-container'],
), [className]);
if (!global.logVisible) {
if (!globalState.logVisible) {
return null;
}

View file

@ -51,6 +51,11 @@ const ROUTES = [
icon: Icons.Box,
name: 'Install APK',
},
{
url: '/power',
icon: Icons.Power,
name: 'Power Menu',
},
];
function NavLink({ link, defaultRender: DefaultRender, ...props }: IComponentAsProps<INavButtonProps>) {

View file

@ -5,7 +5,7 @@ import type { NextPage } from 'next';
import Head from 'next/head';
import React from "react";
import { ExternalLink } from "../components";
import { global } from '../state';
import { globalState } from '../state';
import { Icons, RouteStackProps } from "../utils";
const KNOWN_FEATURES: Record<string, string> = {
@ -52,7 +52,7 @@ const DeviceInfo: NextPage = () => {
</MessageBar>
<span>
<span>Protocol Version: </span>
<code>{global.device?.protocolVersion?.toString(16).padStart(8, '0')}</code>
<code>{globalState.device?.protocolVersion?.toString(16).padStart(8, '0')}</code>
</span>
<Separator />
@ -60,21 +60,21 @@ const DeviceInfo: NextPage = () => {
<code>ro.product.name</code>
<span> field in Android Build Props</span>
</MessageBar>
<span>Product Name: {global.device?.product}</span>
<span>Product Name: {globalState.device?.product}</span>
<Separator />
<MessageBar>
<code>ro.product.model</code>
<span> field in Android Build Props</span>
</MessageBar>
<span>Model Name: {global.device?.model}</span>
<span>Model Name: {globalState.device?.model}</span>
<Separator />
<MessageBar>
<code>ro.product.device</code>
<span> field in Android Build Props</span>
</MessageBar>
<span>Device Name: {global.device?.device}</span>
<span>Device Name: {globalState.device?.device}</span>
<Separator />
<MessageBar>
@ -87,7 +87,7 @@ const DeviceInfo: NextPage = () => {
</MessageBar>
<span>
<span>Features: </span>
{global.device?.features?.map((feature, index) => (
{globalState.device?.features?.map((feature, index) => (
<span key={feature}>
{index !== 0 && (<span>, </span>)}
<span>{feature}</span>

View file

@ -12,7 +12,7 @@ import Router, { useRouter } from "next/router";
import path from 'path';
import React, { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';
import { CommandBar } from '../components';
import { global } from '../state';
import { globalState } from '../state';
import { asyncEffect, chunkFile, formatSize, formatSpeed, Icons, pickFile, RouteStackProps } from '../utils';
let StreamSaver: typeof import('streamsaver');
@ -140,7 +140,7 @@ class FileManagerState {
iconName: Icons.CloudArrowUp,
style: { height: 20, fontSize: 20, lineHeight: 1.5 }
},
disabled: !global.device,
disabled: !globalState.device,
onClick: () => {
(async () => {
const files = await pickFile({ multiple: true });
@ -167,7 +167,7 @@ class FileManagerState {
},
onClick: () => {
(async () => {
const sync = await global.device!.sync();
const sync = await globalState.device!.sync();
try {
const itemPath = path.resolve(this.path, this.selectedItems[0].name!);
const readableStream = createReadableStreamFromBufferIterator(sync.read(itemPath));
@ -177,7 +177,7 @@ class FileManagerState {
});
await readableStream.pipeTo(writeableStream);
} catch (e) {
global.showErrorDialog(e instanceof Error ? e.message : `${e}`);
globalState.showErrorDialog(e instanceof Error ? e.message : `${e}`);
} finally {
sync.dispose();
}
@ -199,14 +199,14 @@ class FileManagerState {
(async () => {
try {
for (const item of this.selectedItems) {
const output = await global.device!.rm(path.resolve(this.path, item.name!));
const output = await globalState.device!.rm(path.resolve(this.path, item.name!));
if (output) {
global.showErrorDialog(output);
globalState.showErrorDialog(output);
return;
}
}
} catch (e) {
global.showErrorDialog(e instanceof Error ? e.message : `${e}`);
globalState.showErrorDialog(e instanceof Error ? e.message : `${e}`);
} finally {
this.loadFiles();
}
@ -359,7 +359,7 @@ class FileManagerState {
});
autorun(() => {
if (global.device) {
if (globalState.device) {
if (this.initial && this.visible) {
this.initial = false;
this.loadFiles();
@ -381,7 +381,7 @@ class FileManagerState {
this.path = path;
if (!global.device) {
if (!globalState.device) {
return;
}
@ -393,13 +393,13 @@ class FileManagerState {
runInAction(() => this.items = []);
if (!global.device) {
if (!globalState.device) {
return;
}
runInAction(() => this.loading = true);
const sync = await global.device.sync();
const sync = await globalState.device.sync();
const items: ListItem[] = [];
const linkItems: AdbSyncEntryResponse[] = [];
@ -456,7 +456,7 @@ class FileManagerState {
});
upload = async (file: File) => {
const sync = await global.device!.sync();
const sync = await globalState.device!.sync();
try {
const itemPath = path.resolve(this.path!, file.name);
runInAction(() => {
@ -491,7 +491,7 @@ class FileManagerState {
clearInterval(intervalId);
}
} catch (e) {
global.showErrorDialog(e instanceof Error ? e.message : `${e}`);
globalState.showErrorDialog(e instanceof Error ? e.message : `${e}`);
} finally {
sync.dispose();
this.loadFiles();
@ -566,7 +566,7 @@ const FileManager: NextPage = (): JSX.Element | null => {
const [previewUrl, setPreviewUrl] = useState<string | undefined>();
const previewImage = useCallback(async (path: string) => {
const sync = await global.device!.sync();
const sync = await globalState.device!.sync();
try {
const readableStream = createReadableStreamFromBufferIterator(sync.read(path));
const response = new Response(readableStream);

View file

@ -6,7 +6,7 @@ import { NextPage } from "next";
import Head from "next/head";
import React, { useCallback, useEffect, useRef } from 'react';
import { CommandBar, DemoMode, DeviceView } from '../components';
import { global } from "../state";
import { globalState } from "../state";
import { Icons, RouteStackProps } from "../utils";
class FrameBufferState {
@ -38,15 +38,15 @@ const FrameBuffer: NextPage = (): JSX.Element | null => {
const canvasRef = useRef<HTMLCanvasElement | null>(null);
const capture = useCallback(async () => {
if (!global.device) {
if (!globalState.device) {
return;
}
try {
const framebuffer = await global.device.framebuffer();
const framebuffer = await globalState.device.framebuffer();
state.setImage(framebuffer);
} catch (e) {
global.showErrorDialog(e instanceof Error ? e.message : `${e}`);
globalState.showErrorDialog(e instanceof Error ? e.message : `${e}`);
}
}, []);
@ -65,7 +65,7 @@ const FrameBuffer: NextPage = (): JSX.Element | null => {
const commandBarItems = computed(() => [
{
key: 'start',
disabled: !global.device,
disabled: !globalState.device,
iconProps: { iconName: Icons.Camera, style: { height: 20, fontSize: 20, lineHeight: 1.5 } },
text: 'Capture',
onClick: capture,
@ -84,7 +84,7 @@ const FrameBuffer: NextPage = (): JSX.Element | null => {
const url = canvas.toDataURL();
const a = document.createElement('a');
a.href = url;
a.download = `Screenshot of ${global.device!.name}.png`;
a.download = `Screenshot of ${globalState.device!.name}.png`;
a.click();
},
},

View file

@ -5,7 +5,7 @@ import { observer } from "mobx-react-lite";
import { NextPage } from "next";
import Head from "next/head";
import React from "react";
import { global } from "../state";
import { globalState } from "../state";
import { chunkFile, pickFile, RouteStackProps } from "../utils";
enum Stage {
@ -57,7 +57,7 @@ class InstallPageState {
};
});
await global.device!.install(chunkFile(file, AdbSyncMaxPacketSize), uploaded => {
await globalState.device!.install(chunkFile(file, AdbSyncMaxPacketSize), uploaded => {
runInAction(() => {
if (uploaded !== file.size) {
this.progress = {
@ -103,7 +103,7 @@ const Install: NextPage = () => {
<Stack horizontal>
<DefaultButton
disabled={!global.device || state.installing}
disabled={!globalState.device || state.installing}
text="Open"
onClick={state.install}
/>

48
apps/demo/pages/power.tsx Normal file
View file

@ -0,0 +1,48 @@
import { DefaultButton, Icon, MessageBar, TooltipHost } from "@fluentui/react";
import { observer } from "mobx-react-lite";
import { NextPage } from "next";
import { globalState } from "../state";
import { Icons } from "../utils";
const Power: NextPage = () => {
return (
<div style={{ padding: 20 }}>
<div>
<DefaultButton text="Reboot" disabled={!globalState.device} onClick={() => globalState.device!.power.reboot()} />
</div>
<div style={{ marginTop: 20 }}>
<DefaultButton text="Reboot to Bootloader" disabled={!globalState.device} onClick={() => globalState.device!.power.bootloader()} />
</div>
<div style={{ marginTop: 20 }}>
<DefaultButton text="Reboot to Fastboot" disabled={!globalState.device} onClick={() => globalState.device!.power.fastboot()} />
</div>
<div style={{ marginTop: 20 }}>
<DefaultButton text="Reboot to Recovery" disabled={!globalState.device} onClick={() => globalState.device!.power.recovery()} />
</div>
<div style={{ marginTop: 20 }}>
<DefaultButton text="Reboot to Sideload" disabled={!globalState.device} onClick={() => globalState.device!.power.sideload()} />
</div>
<div style={{ marginTop: 20 }}>
<DefaultButton text="Reboot to Qualcomm EDL Mode" disabled={!globalState.device} onClick={() => globalState.device!.power.qualcommEdlMode()} />
<TooltipHost content={<span>Only works on some Qualcomm devices.</span>}>
<Icon style={{ verticalAlign: 'middle', marginLeft: 4, fontSize: 18 }} iconName={Icons.Info} />
</TooltipHost>
</div>
<div style={{ marginTop: 20 }}>
<DefaultButton text="Power Off" disabled={!globalState.device} onClick={() => globalState.device!.power.powerOff()} />
</div>
<div style={{ marginTop: 20 }}>
<DefaultButton text="Press Power Button" disabled={!globalState.device} onClick={() => globalState.device!.power.powerButton()} />
</div>
</div>
);
};
export default observer(Power);

View file

@ -8,7 +8,7 @@ import { NextPage } from "next";
import Head from "next/head";
import React, { useEffect, useState } from "react";
import { DemoMode, DeviceView, DeviceViewRef, ExternalLink } from "../components";
import { global } from "../state";
import { globalState } from "../state";
import { CommonStackTokens, formatSpeed, Icons, RouteStackProps } from "../utils";
import SCRCPY_SERVER_VERSION from '@yume-chan/scrcpy/bin/version';
@ -201,7 +201,7 @@ class ScrcpyPageState {
if (!this.running) {
result.push({
key: 'start',
disabled: !global.device,
disabled: !globalState.device,
iconProps: { iconName: Icons.Play },
text: 'Start',
onClick: this.start as VoidFunction,
@ -310,7 +310,7 @@ class ScrcpyPageState {
});
autorun(() => {
if (global.device) {
if (globalState.device) {
runInAction(() => {
this.encoders = [];
this.selectedEncoder = undefined;
@ -351,7 +351,7 @@ class ScrcpyPageState {
}
start = async () => {
if (!global.device) {
if (!globalState.device) {
return;
}
@ -395,7 +395,7 @@ class ScrcpyPageState {
}), 1000);
try {
await pushServer(global.device, serverBuffer, {
await pushServer(globalState.device, serverBuffer, {
onProgress: action((progress) => {
this.serverUploadedSize = progress;
}),
@ -409,7 +409,7 @@ class ScrcpyPageState {
}
const encoders = await ScrcpyClient.getEncoders(
global.device,
globalState.device,
DEFAULT_SERVER_PATH,
SCRCPY_SERVER_VERSION,
new ScrcpyOptions1_21({
@ -428,7 +428,7 @@ class ScrcpyPageState {
// Run scrcpy once will delete the server file
// Re-push it
await pushServer(global.device, serverBuffer);
await pushServer(globalState.device, serverBuffer);
const factory = this.selectedDecoder.factory;
const decoder = new factory();
@ -436,7 +436,7 @@ class ScrcpyPageState {
this.decoder = decoder;
});
const client = new ScrcpyClient(global.device);
const client = new ScrcpyClient(globalState.device);
runInAction(() => this.log = []);
client.onOutput(action(line => this.log.push(line)));
client.onClose(this.stop);
@ -487,7 +487,7 @@ class ScrcpyPageState {
this.running = true;
});
} catch (e: any) {
global.showErrorDialog(e.message);
globalState.showErrorDialog(e.message);
} finally {
runInAction(() => {
this.connecting = false;

View file

@ -5,7 +5,7 @@ import { NextPage } from "next";
import Head from "next/head";
import React, { CSSProperties, useCallback, useEffect, useRef, useState } from 'react';
import 'xterm/css/xterm.css';
import { global } from "../state";
import { globalState } from "../state";
import { Icons, ResizeObserver, RouteStackProps } from '../utils';
let terminal: import('../components/terminal').AdbTerminal;
@ -40,9 +40,9 @@ const Shell: NextPage = (): JSX.Element | null => {
const connectingRef = useRef(false);
useEffect(() => {
return reaction(
() => global.device,
() => globalState.device,
async () => {
if (!global.device) {
if (!globalState.device) {
terminal.socket = undefined;
return;
}
@ -53,10 +53,10 @@ const Shell: NextPage = (): JSX.Element | null => {
try {
connectingRef.current = true;
const socket = await global.device.childProcess.shell();
const socket = await globalState.device.childProcess.shell();
terminal.socket = socket;
} catch (e) {
global.showErrorDialog(e instanceof Error ? e.message : `${e}`);
globalState.showErrorDialog(e instanceof Error ? e.message : `${e}`);
} finally {
connectingRef.current = false;
}

View file

@ -5,7 +5,7 @@ import { NextPage } from "next";
import Head from "next/head";
import React, { useCallback, useEffect } from "react";
import { ExternalLink } from "../components";
import { global } from "../state";
import { globalState } from "../state";
import { asyncEffect, Icons, RouteStackProps } from "../utils";
class TcpIpState {
@ -26,7 +26,7 @@ class TcpIpState {
autorun(() => {
if (global.device) {
if (globalState.device) {
if (this.initial && this.visible) {
this.initial = false;
this.queryInfo();
@ -41,14 +41,14 @@ class TcpIpState {
return [
{
key: 'refresh',
disabled: !global.device,
disabled: !globalState.device,
iconProps: { iconName: Icons.ArrowClockwise },
text: 'Refresh',
onClick: this.queryInfo as VoidFunction,
},
{
key: 'apply',
disabled: !global.device,
disabled: !globalState.device,
iconProps: { iconName: Icons.Save },
text: 'Apply',
onClick: this.applyServicePort,
@ -57,7 +57,7 @@ class TcpIpState {
}
queryInfo = asyncEffect(async (signal) => {
if (!global.device) {
if (!globalState.device) {
runInAction(() => {
this.serviceListenAddresses = undefined;
this.servicePortEnabled = false;
@ -68,9 +68,9 @@ class TcpIpState {
return;
}
const serviceListenAddresses = await global.device.getProp('service.adb.listen_addrs');
const servicePort = await global.device.getProp('service.adb.tcp.port');
const persistPort = await global.device.getProp('persist.adb.tcp.port');
const serviceListenAddresses = await globalState.device.getProp('service.adb.listen_addrs');
const servicePort = await globalState.device.getProp('service.adb.tcp.port');
const persistPort = await globalState.device.getProp('persist.adb.tcp.port');
if (signal.aborted) {
return;
@ -98,14 +98,14 @@ class TcpIpState {
});
applyServicePort = async () => {
if (!global.device) {
if (!globalState.device) {
return;
}
if (state.servicePortEnabled) {
await global.device.tcpip.setPort(Number.parseInt(state.servicePort, 10));
await globalState.device.tcpip.setPort(Number.parseInt(state.servicePort, 10));
} else {
await global.device.tcpip.disable();
await globalState.device.tcpip.disable();
}
};
}
@ -183,12 +183,12 @@ const TcpIp: NextPage = () => {
inlineLabel
label="service.adb.tcp.port"
checked={state.servicePortEnabled}
disabled={!global.device || !!state.serviceListenAddresses}
disabled={!globalState.device || !!state.serviceListenAddresses}
onText="Enabled"
offText="Disabled"
onChange={handleServicePortEnabledChange}
/>
{global && (
{globalState && (
<TextField
disabled={!!state.serviceListenAddresses}
value={state.servicePort}

View file

@ -36,4 +36,4 @@ export class GlobalState {
}
}
export const global = new GlobalState();
export const globalState = new GlobalState();

View file

@ -1,5 +1,5 @@
import { registerIcons } from "@fluentui/react";
import { MoreHorizontalRegular, AddCircleRegular, ArrowClockwiseRegular, ArrowSortDownRegular, ArrowSortUpRegular, BookmarkRegular, BoxRegular, CameraRegular, CheckmarkRegular, ChevronDownRegular, ChevronRightRegular, ChevronUpRegular, CircleRegular, CloudArrowDownRegular, CloudArrowUpRegular, CopyRegular, DeleteRegular, DocumentRegular, FolderRegular, FullScreenMaximizeRegular, InfoRegular, NavigationRegular, PersonFeedbackRegular, PhoneLaptopRegular, PhoneRegular, PlayRegular, PlugConnectedRegular, PlugDisconnectedRegular, SaveRegular, SearchRegular, SettingsRegular, StopRegular, TextGrammarErrorRegular, WandRegular, WifiSettingsRegular, WindowConsoleRegular } from '@fluentui/react-icons';
import { AddCircleRegular, ArrowClockwiseRegular, ArrowSortDownRegular, ArrowSortUpRegular, BookmarkRegular, BoxRegular, CameraRegular, CheckmarkRegular, ChevronDownRegular, ChevronRightRegular, ChevronUpRegular, CircleRegular, CloudArrowDownRegular, CloudArrowUpRegular, CopyRegular, DeleteRegular, DocumentRegular, FolderRegular, FullScreenMaximizeRegular, InfoRegular, MoreHorizontalRegular, NavigationRegular, PersonFeedbackRegular, PhoneLaptopRegular, PhoneRegular, PlayRegular, PlugConnectedRegular, PlugDisconnectedRegular, PowerRegular, SaveRegular, SearchRegular, SettingsRegular, StopRegular, TextGrammarErrorRegular, WandRegular, WifiSettingsRegular, WindowConsoleRegular } from '@fluentui/react-icons';
const STYLE = {};
@ -30,6 +30,7 @@ export function register() {
Play: <PlayRegular style={STYLE} />,
PlugConnected: <PlugConnectedRegular style={STYLE} />,
PlugDisconnected: <PlugDisconnectedRegular style={STYLE} />,
Power: <PowerRegular style={STYLE} />,
Save: <SaveRegular style={STYLE} />,
Settings: <SettingsRegular style={STYLE} />,
Stop: <StopRegular style={STYLE} />,
@ -77,6 +78,7 @@ export default {
Play: 'Play',
PlugConnected: 'PlugConnected',
PlugDisconnected: 'PlugDisconnected',
Power: 'Power',
Save: 'Save',
Settings: 'Settings',
Stop: 'Stop',

View file

@ -2,7 +2,7 @@ import { PromiseResolver } from '@yume-chan/async';
import { DisposableList } from '@yume-chan/event';
import { AdbAuthenticationHandler, AdbCredentialStore, AdbDefaultAuthenticators } from './auth';
import { AdbBackend } from './backend';
import { AdbChildProcess, AdbDemoMode, AdbFrameBuffer, AdbReverseCommand, AdbSync, AdbTcpIpCommand, escapeArg, framebuffer, install } from './commands';
import { AdbChildProcess, AdbDemoMode, AdbFrameBuffer, AdbPower, AdbReverseCommand, AdbSync, AdbTcpIpCommand, escapeArg, framebuffer, install } from './commands';
import { AdbFeatures } from './features';
import { AdbCommand } from './packet';
import { AdbLogger, AdbPacketDispatcher, AdbSocket } from './socket';
@ -44,22 +44,21 @@ export class Adb {
private _features: AdbFeatures[] | undefined;
public get features() { return this._features; }
public readonly tcpip: AdbTcpIpCommand;
public readonly reverse: AdbReverseCommand;
public readonly demoMode: AdbDemoMode;
public readonly childProcess: AdbChildProcess;
public readonly demoMode: AdbDemoMode;
public readonly power: AdbPower;
public readonly reverse: AdbReverseCommand;
public readonly tcpip: AdbTcpIpCommand;
public constructor(backend: AdbBackend, logger?: AdbLogger) {
this._backend = backend;
this.packetDispatcher = new AdbPacketDispatcher(backend, logger);
this.tcpip = new AdbTcpIpCommand(this);
this.reverse = new AdbReverseCommand(this.packetDispatcher);
this.demoMode = new AdbDemoMode(this);
this.childProcess = new AdbChildProcess(this);
this.demoMode = new AdbDemoMode(this);
this.power = new AdbPower(this);
this.reverse = new AdbReverseCommand(this.packetDispatcher);
this.tcpip = new AdbTcpIpCommand(this);
backend.onDisconnected(this.dispose, this);
}

View file

@ -2,6 +2,7 @@ export * from './base';
export * from './demo-mode';
export * from './framebuffer';
export * from './install';
export * from './power';
export * from './reverse';
export * from './shell';
export * from './sync';

View file

@ -0,0 +1,35 @@
import { AdbCommandBase } from "./base";
export class AdbPower extends AdbCommandBase {
public reboot(name: string = '') {
return this.adb.createSocketAndReadAll(`reboot:${name}`);
}
public bootloader() {
return this.reboot('bootloader');
}
public fastboot() {
return this.reboot('fastboot');
}
public recovery() {
return this.reboot('recovery');
}
public sideload() {
return this.reboot('sideload');
}
public qualcommEdlMode() {
return this.reboot('edl');
}
public powerOff() {
return this.adb.childProcess.exec('reboot', '-p');
}
public powerButton(longPress: boolean = false) {
return this.adb.childProcess.exec('input', 'keyevent', longPress ? '--longpress POWER' : 'POWER');
}
}

View file

@ -51,7 +51,7 @@ export class AdbReverseCommand extends AutoDisposable {
}
const address = decodeUtf8(e.packet.payload!);
// tcp:1234\0
// tcp:12345\0
const port = Number.parseInt(address.substring(4));
if (this.localPortToHandler.has(port)) {
this.localPortToHandler.get(port)!.onSocket(e.packet, e.socket);