Fixed: fixed bug scrcpy and shell is disconnect when switch page
This commit is contained in:
parent
9543d4541c
commit
0fe469b5c6
43 changed files with 1378 additions and 1366 deletions
|
|
@ -1,11 +1,10 @@
|
|||
import { Button } from '@/components/ui/button'
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { ReloadIcon } from '@radix-ui/react-icons'
|
||||
import { LinuxFileType, type Adb } from '@yume-chan/adb'
|
||||
import { WritableStream } from '@yume-chan/stream-extra'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { Consumable, WritableStream, ReadableStream } from '@yume-chan/stream-extra'
|
||||
import JSZip from 'jszip'
|
||||
import { useCallback, useState } from 'react'
|
||||
|
||||
interface FileManagerTabProps {
|
||||
adb: Adb | undefined
|
||||
|
|
@ -20,9 +19,32 @@ type filesType = {
|
|||
|
||||
export const FileManagerTab: React.FC<FileManagerTabProps> = ({ adb }) => {
|
||||
const [path, setPath] = useState<string>('')
|
||||
const [pushPath, setPushPath] = useState<string>('')
|
||||
const [pushFile, setPushFile] = useState<File | null>()
|
||||
|
||||
const [files, setFiles] = useState<filesType>()
|
||||
|
||||
const pushFiles = async (filename: string, blob: Blob, targetPath: string) => {
|
||||
if (!adb) return
|
||||
|
||||
const buffer = await blob.arrayBuffer()
|
||||
console.log(blob, buffer, targetPath, filename)
|
||||
const sync = await adb.sync()
|
||||
try {
|
||||
await sync.write({
|
||||
filename: targetPath + '/' + filename,
|
||||
file: new ReadableStream({
|
||||
start(controller) {
|
||||
controller.enqueue(new Consumable(new Uint8Array(buffer)))
|
||||
controller.close()
|
||||
}
|
||||
})
|
||||
})
|
||||
} finally {
|
||||
await sync.dispose()
|
||||
}
|
||||
}
|
||||
|
||||
const zipFiles = (files: filesType) => {
|
||||
const zip = new JSZip()
|
||||
|
||||
|
|
@ -132,10 +154,6 @@ export const FileManagerTab: React.FC<FileManagerTabProps> = ({ adb }) => {
|
|||
}
|
||||
}, [adb, path])
|
||||
|
||||
const refresh = useCallback(() => {
|
||||
console.log('Refreshing...')
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
|
|
@ -143,19 +161,29 @@ export const FileManagerTab: React.FC<FileManagerTabProps> = ({ adb }) => {
|
|||
<CardDescription>Manage files in Android</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="flex flex-col items-center justify-end py-3 w-full">
|
||||
<div className="flex w-full items-center justify-end">
|
||||
<Button variant={'outline'} onClick={refresh}>
|
||||
<ReloadIcon />
|
||||
</Button>
|
||||
<div className="flex w-full flex-col items-center justify-end py-3">
|
||||
<div className="flex w-full items-center justify-around space-x-10">
|
||||
<div className="flex space-x-5">
|
||||
<Input placeholder="folder to download" value={path} onChange={e => setPath(e.target.value)} />
|
||||
<Button variant={'default'} onClick={download}>
|
||||
Download
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex space-x-5">
|
||||
<Input placeholder="path to push file" value={pushPath} onChange={e => setPushPath(e.target.value)} />
|
||||
<Input type="file" accept="*" dir="ltr" onChange={e => setPushFile(e.target.files?.item(0))} />
|
||||
<Button
|
||||
variant={'default'}
|
||||
onClick={
|
||||
() => pushFiles(pushFile?.name || 'unname', pushFile as Blob, pushPath)
|
||||
//testPushFile
|
||||
}
|
||||
>
|
||||
Push
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex space-x-5 w-96">
|
||||
<Input placeholder="folder to download" value={path} onChange={e => setPath(e.target.value)} />
|
||||
<Button variant={'default'} onClick={download}>
|
||||
Download
|
||||
</Button>
|
||||
</div>
|
||||
<div className="w-full max-h-96 overflow-y-auto">{files && <FileTree files={files} />}</div>
|
||||
<div className="max-h-96 w-full overflow-y-auto">{files && <FileTree files={files} />}</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
|
|
|||
|
|
@ -1,269 +1,38 @@
|
|||
import { Button } from '@/components/ui/button'
|
||||
import { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter } from '@/components/ui/card'
|
||||
import { toast } from '@/components/ui/use-toast'
|
||||
import { type Adb, type AdbSubprocessProtocol } from '@yume-chan/adb'
|
||||
import { AdbScrcpyClient, AdbScrcpyOptions1_22 } from '@yume-chan/adb-scrcpy'
|
||||
import {
|
||||
AndroidKeyCode,
|
||||
AndroidKeyEventAction,
|
||||
AndroidMotionEventAction,
|
||||
ScrcpyLogLevel1_18,
|
||||
ScrcpyOptions1_25,
|
||||
ScrcpyPointerId,
|
||||
ScrcpyVideoCodecId
|
||||
} from '@yume-chan/scrcpy'
|
||||
import { WebCodecsDecoder } from '@yume-chan/scrcpy-decoder-webcodecs'
|
||||
import { Consumable, WritableStream, ReadableStream, DecodeUtf8Stream } from '@yume-chan/stream-extra'
|
||||
import { memo, useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { Terminal } from 'xterm'
|
||||
import { FitAddon } from 'xterm-addon-fit'
|
||||
|
||||
import { Card, CardHeader, CardTitle, CardDescription, CardContent } from '@/components/ui/card'
|
||||
import useScrcpy from '@/hooks/scrcpy-android'
|
||||
import { type Adb } from '@yume-chan/adb'
|
||||
import { memo, useEffect, useRef } from 'react'
|
||||
import 'xterm/css/xterm.css'
|
||||
import { useShallow } from 'zustand/react/shallow'
|
||||
|
||||
interface ScrcpyTabProps {
|
||||
adb: Adb | undefined
|
||||
}
|
||||
|
||||
export const ScrcpyTab: React.FC<ScrcpyTabProps> = memo(({ adb }) => {
|
||||
const logcatRef = useRef<HTMLDivElement>(null)
|
||||
const scrcpyScreenRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
const [process, setProcess] = useState<AdbSubprocessProtocol | undefined>()
|
||||
const [client, setClient] = useState<AdbScrcpyClient | undefined>()
|
||||
const [decoder, setDecoder] = useState<WebCodecsDecoder | undefined>()
|
||||
|
||||
console.log('rendering scrcpy tab')
|
||||
const { scrcpyClient, decoder, connectScrcpy, onHomeClick, onBackClick, disconnectScrcpy } = useScrcpy(
|
||||
useShallow(state => ({
|
||||
scrcpyClient: state.scrcpyClient,
|
||||
decoder: state.decoder,
|
||||
connectScrcpy: state.connectScrcpy,
|
||||
onHomeClick: state.onHomeClick,
|
||||
onBackClick: state.onBackClick,
|
||||
disconnectScrcpy: state.disconnectScrcpy
|
||||
}))
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
const startTerminal = async () => {
|
||||
if (logcatRef.current && adb) {
|
||||
const terminal: Terminal = new Terminal()
|
||||
const fitAddon = new FitAddon()
|
||||
terminal.loadAddon(fitAddon)
|
||||
|
||||
const process: AdbSubprocessProtocol = await adb.subprocess.shell('logcat')
|
||||
process.stdout.pipeTo(
|
||||
new WritableStream<Uint8Array>({
|
||||
write(chunk) {
|
||||
terminal.write(chunk)
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
terminal.options.disableStdin = true
|
||||
terminal.options.theme = {
|
||||
background: '#1e1e1e',
|
||||
foreground: '#d4d4d4'
|
||||
}
|
||||
|
||||
terminal.open(logcatRef.current)
|
||||
fitAddon.fit()
|
||||
setProcess(process)
|
||||
}
|
||||
if (decoder) {
|
||||
scrcpyScreenRef.current?.appendChild(decoder.renderer)
|
||||
decoder.renderer.style.width = '100%'
|
||||
decoder.renderer.style.height = '100%'
|
||||
} else {
|
||||
if (scrcpyScreenRef.current) scrcpyScreenRef.current.innerHTML = ''
|
||||
}
|
||||
|
||||
startTerminal()
|
||||
|
||||
return () => {
|
||||
console.log('cleaning up logcat')
|
||||
|
||||
for (const child of logcatRef.current?.children || []) {
|
||||
logcatRef.current?.removeChild(child)
|
||||
}
|
||||
|
||||
process?.stderr.cancel()
|
||||
process?.stdout.cancel()
|
||||
process?.kill()
|
||||
}
|
||||
}, [adb])
|
||||
|
||||
const connectScrcpy = useCallback(async () => {
|
||||
if (!adb) {
|
||||
toast({
|
||||
title: 'No ADB connection',
|
||||
description: 'Please connect to a device first',
|
||||
duration: 3000,
|
||||
variant: 'destructive'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// clean up the scrcpy screen
|
||||
if (scrcpyScreenRef.current && scrcpyScreenRef.current.children.length > 0) {
|
||||
while (scrcpyScreenRef.current.firstChild) {
|
||||
scrcpyScreenRef.current.removeChild(scrcpyScreenRef.current.firstChild)
|
||||
}
|
||||
}
|
||||
|
||||
// fetch the scrcpy server binary
|
||||
// TODO: should load from real server instead of local file. Fix this later
|
||||
const server: ArrayBuffer = await fetch(new URL('../../../scrcpy/scrcpy_server_v1.25', import.meta.url)).then(res =>
|
||||
res.arrayBuffer()
|
||||
)
|
||||
|
||||
// push the server binary to the device
|
||||
const sync = await adb.sync()
|
||||
try {
|
||||
await sync.write({
|
||||
filename: '/data/local/tmp/scrcpy-server.jar',
|
||||
file: new ReadableStream({
|
||||
start(controller) {
|
||||
controller.enqueue(new Consumable(new Uint8Array(server)))
|
||||
controller.close()
|
||||
}
|
||||
})
|
||||
})
|
||||
} finally {
|
||||
await sync.dispose()
|
||||
}
|
||||
|
||||
// start the scrcpy server
|
||||
const res = await adb.subprocess.spawn(
|
||||
'CLASSPATH=/data/local/tmp/scrcpy-server.jar app_process / com.genymobile.scrcpy.Server 1.25'
|
||||
)
|
||||
|
||||
// pipe the server output to the console
|
||||
res.stdout.pipeThrough(new DecodeUtf8Stream()).pipeTo(
|
||||
new WritableStream({
|
||||
write(chunk) {
|
||||
console.log(chunk)
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
const scrcpyOption = new ScrcpyOptions1_25({
|
||||
maxFps: 60,
|
||||
bitRate: 4000000,
|
||||
stayAwake: true,
|
||||
control: true,
|
||||
logLevel: ScrcpyLogLevel1_18.Debug
|
||||
})
|
||||
|
||||
// start the scrcpy client
|
||||
const _client = await AdbScrcpyClient.start(
|
||||
adb,
|
||||
'/data/local/tmp/scrcpy-server.jar',
|
||||
'1.25',
|
||||
new AdbScrcpyOptions1_22(scrcpyOption)
|
||||
)
|
||||
|
||||
// get the video stream
|
||||
const videoStream = await _client?.videoStream
|
||||
|
||||
// create a decoder
|
||||
const _decoder = new WebCodecsDecoder(ScrcpyVideoCodecId.H264)
|
||||
scrcpyScreenRef.current?.appendChild(_decoder.renderer)
|
||||
_decoder.renderer.style.width = '100%'
|
||||
_decoder.renderer.style.height = '100%'
|
||||
|
||||
// pipe the video stream to the decoder
|
||||
videoStream?.stream.pipeTo(_decoder.writable)
|
||||
|
||||
// if client has controlMessageWriter, Inject mouse and button events
|
||||
if (_client.controlMessageWriter) {
|
||||
_decoder.renderer.addEventListener('mousedown', e => {
|
||||
// client width and height 450 x 800
|
||||
const react = _decoder.renderer.getBoundingClientRect()
|
||||
|
||||
// normalize to _decoder.renderer.width and height 1080 x 1920
|
||||
const x = ((e.clientX - react.left) * _decoder.renderer.width) / react.width
|
||||
const y = ((e.clientY - react.top) * _decoder.renderer.height) / react.height
|
||||
|
||||
//console.log('mouse down at ' + x + ' ' + y)
|
||||
_client.controlMessageWriter?.injectTouch({
|
||||
action: AndroidMotionEventAction.Down,
|
||||
pointerId: ScrcpyPointerId.Mouse | ScrcpyPointerId.Finger,
|
||||
pointerX: x,
|
||||
pointerY: y,
|
||||
pressure: 1,
|
||||
screenWidth: _decoder.renderer.width,
|
||||
screenHeight: _decoder.renderer.height,
|
||||
buttons: 0,
|
||||
actionButton: 0
|
||||
})
|
||||
})
|
||||
|
||||
_decoder.renderer.addEventListener('mouseup', e => {
|
||||
// client width and height 450 x 800
|
||||
const react = _decoder.renderer.getBoundingClientRect()
|
||||
|
||||
// normalize to _decoder.renderer.width and height 1080 x 1920
|
||||
const x = ((e.clientX - react.left) * _decoder.renderer.width) / react.width
|
||||
const y = ((e.clientY - react.top) * _decoder.renderer.height) / react.height
|
||||
|
||||
//console.log('mouse up at ' + x + ' ' + y)
|
||||
_client.controlMessageWriter?.injectTouch({
|
||||
action: AndroidMotionEventAction.Up,
|
||||
pointerId: ScrcpyPointerId.Mouse,
|
||||
pointerX: x,
|
||||
pointerY: y,
|
||||
pressure: 1,
|
||||
screenWidth: _decoder.renderer.width,
|
||||
screenHeight: _decoder.renderer.height,
|
||||
buttons: 0,
|
||||
actionButton: 0
|
||||
})
|
||||
})
|
||||
|
||||
_decoder.renderer.addEventListener('mousemove', e => {
|
||||
// client width and height 450 x 800
|
||||
const react = _decoder.renderer.getBoundingClientRect()
|
||||
|
||||
// normalize to _decoder.renderer.width and height 1080 x 1920
|
||||
const x = ((e.clientX - react.left) * _decoder.renderer.width) / react.width
|
||||
const y = ((e.clientY - react.top) * _decoder.renderer.height) / react.height
|
||||
|
||||
//console.log('mouse move at ' + x + ' ' + y)
|
||||
_client.controlMessageWriter?.injectTouch({
|
||||
action: AndroidMotionEventAction.Move,
|
||||
pointerId: ScrcpyPointerId.Mouse,
|
||||
pointerX: x,
|
||||
pointerY: y,
|
||||
pressure: 1,
|
||||
screenWidth: _decoder.renderer.width,
|
||||
screenHeight: _decoder.renderer.height,
|
||||
buttons: 0,
|
||||
actionButton: 0
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
setDecoder(_decoder)
|
||||
setClient(_client)
|
||||
}, [adb, scrcpyScreenRef])
|
||||
|
||||
function onHomeClickHandler() {
|
||||
client?.controlMessageWriter?.injectKeyCode({
|
||||
action: AndroidKeyEventAction.Up,
|
||||
keyCode: AndroidKeyCode.AndroidHome,
|
||||
metaState: 0,
|
||||
repeat: 0
|
||||
})
|
||||
}
|
||||
|
||||
function onBackClickHandler() {
|
||||
client?.controlMessageWriter?.injectKeyCode({
|
||||
action: AndroidKeyEventAction.Up,
|
||||
keyCode: AndroidKeyCode.AndroidBack,
|
||||
metaState: 0,
|
||||
repeat: 0
|
||||
})
|
||||
}
|
||||
|
||||
function disconnectScrcpy() {
|
||||
// clean ref
|
||||
if (scrcpyScreenRef.current && scrcpyScreenRef.current.children.length > 0) {
|
||||
while (scrcpyScreenRef.current.firstChild) {
|
||||
scrcpyScreenRef.current.removeChild(scrcpyScreenRef.current.firstChild)
|
||||
}
|
||||
}
|
||||
|
||||
decoder?.dispose()
|
||||
client?.close()
|
||||
|
||||
setClient(undefined)
|
||||
setDecoder(undefined)
|
||||
}
|
||||
}, [decoder])
|
||||
|
||||
return (
|
||||
<Card>
|
||||
|
|
@ -271,54 +40,44 @@ export const ScrcpyTab: React.FC<ScrcpyTabProps> = memo(({ adb }) => {
|
|||
<CardTitle>Scrcpy</CardTitle>
|
||||
<CardDescription>Stream and control your Android device from your computer</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="flex w-full justify-around items-start">
|
||||
<CardContent className="flex w-full items-start justify-around">
|
||||
<div>
|
||||
<div className="w-[450px] max-w-[450px] h-[800px] max-h-[800px] bg-slate-700" ref={scrcpyScreenRef} />
|
||||
<div className="flex pt-3 justify-center items-center space-x-4 w-[450px]">
|
||||
<Button onClick={onHomeClickHandler} variant={'outline'} className="flex-1">
|
||||
<div className="h-[800px] max-h-[800px] w-[450px] max-w-[450px] bg-slate-700" ref={scrcpyScreenRef} />
|
||||
<div className="flex w-[450px] items-center justify-center space-x-4 pt-3">
|
||||
<Button onClick={onHomeClick} variant={'outline'} className="flex-1">
|
||||
Home
|
||||
</Button>
|
||||
<Button onClick={onBackClickHandler} variant={'outline'} className="flex-1">
|
||||
<Button onClick={onBackClick} variant={'outline'} className="flex-1">
|
||||
Back
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col space-y-4 w-full px-5">
|
||||
<div className="flex w-full flex-col space-y-4 px-5">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Control</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="flex space-x-4 items-center">
|
||||
{client ? (
|
||||
<div className="flex items-center space-x-4">
|
||||
{scrcpyClient ? (
|
||||
<Button onClick={disconnectScrcpy} variant="destructive">
|
||||
Disconnect
|
||||
</Button>
|
||||
) : (
|
||||
<Button onClick={connectScrcpy} variant="default">
|
||||
<Button
|
||||
onClick={() => {
|
||||
connectScrcpy(adb)
|
||||
}}
|
||||
variant="default"
|
||||
>
|
||||
Connect
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* logcat card */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Logcat</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="flex space-x-4 items-center">
|
||||
<div className="w-full h-96 bg-slate-700" ref={logcatRef} />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</CardContent>
|
||||
<CardFooter>
|
||||
<Button>Save changes</Button>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,84 +1,27 @@
|
|||
import { Button } from '@/components/ui/button'
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { encodeUtf8, type AdbSubprocessProtocol, type Adb } from '@yume-chan/adb'
|
||||
import { Consumable, WritableStream } from '@yume-chan/stream-extra'
|
||||
import { memo, useCallback, useEffect, useRef, useState } from 'react'
|
||||
import useShellAndroid from '@/hooks/shell-android'
|
||||
import { type Adb } from '@yume-chan/adb'
|
||||
import { memo, useEffect, useRef } from 'react'
|
||||
import { type Terminal } from 'xterm'
|
||||
|
||||
import { Terminal } from 'xterm'
|
||||
import { FitAddon } from 'xterm-addon-fit'
|
||||
|
||||
import 'xterm/css/xterm.css'
|
||||
import { useShallow } from 'zustand/react/shallow'
|
||||
|
||||
interface ShellTabProps {
|
||||
adb: Adb | undefined
|
||||
}
|
||||
|
||||
export const ShellTab: React.FC<ShellTabProps> = memo(({ adb }) => {
|
||||
const [process, setProcess] = useState<AdbSubprocessProtocol | undefined>()
|
||||
const [terminal, setTerminal] = useState<Terminal | undefined>()
|
||||
|
||||
const [reader, setReader] = useState<WritableStream<Uint8Array> | undefined>()
|
||||
|
||||
useEffect(() => {
|
||||
if (adb) {
|
||||
console.log('adb is connected')
|
||||
|
||||
console.log('creating terminal')
|
||||
const terminal: Terminal = new Terminal()
|
||||
terminal.options.cursorBlink = true
|
||||
terminal.options.theme = {
|
||||
background: '#1e1e1e',
|
||||
foreground: '#d4d4d4'
|
||||
}
|
||||
|
||||
console.log('creating process')
|
||||
|
||||
const _reader = new WritableStream<Uint8Array>({
|
||||
write(chunk) {
|
||||
terminal.write(chunk)
|
||||
}
|
||||
})
|
||||
|
||||
adb.subprocess.shell('/data/data/com.termux/files/usr/bin/telnet localhost 45515').then(_process => {
|
||||
_process.stdout.pipeTo(_reader)
|
||||
|
||||
const writer = _process.stdin.getWriter()
|
||||
terminal.onData(data => {
|
||||
const buffer = encodeUtf8(data)
|
||||
const consumable = new Consumable(buffer)
|
||||
writer.write(consumable)
|
||||
})
|
||||
|
||||
setReader(_reader)
|
||||
setProcess(_process)
|
||||
setTerminal(terminal)
|
||||
})
|
||||
} else {
|
||||
console.log('adb is not connected')
|
||||
if (process) {
|
||||
process?.stdout.cancel()
|
||||
process?.stderr.cancel()
|
||||
process?.stdin.close()
|
||||
process?.kill()
|
||||
}
|
||||
|
||||
setProcess(undefined)
|
||||
setTerminal(undefined)
|
||||
}
|
||||
}, [adb])
|
||||
|
||||
const killProcess = useCallback(() => {
|
||||
console.log('killing shell')
|
||||
console.log(process)
|
||||
if (process && terminal) {
|
||||
terminal.write('exit\n')
|
||||
reader?.close()
|
||||
process.stderr.cancel()
|
||||
process.stdin.close()
|
||||
process.stdout.cancel()
|
||||
process.kill()
|
||||
}
|
||||
}, [process])
|
||||
const { terminal, startTerminal, killTerminal } = useShellAndroid(
|
||||
useShallow(state => ({
|
||||
terminal: state.terminal,
|
||||
startTerminal: state.startTerminal,
|
||||
killTerminal: state.killTerminal
|
||||
}))
|
||||
)
|
||||
|
||||
return (
|
||||
<Card>
|
||||
|
|
@ -88,10 +31,16 @@ export const ShellTab: React.FC<ShellTabProps> = memo(({ adb }) => {
|
|||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="flex items-center justify-end py-3 w-full">
|
||||
<div>
|
||||
<Button variant={'destructive'} onClick={killProcess}>
|
||||
Kill Shell
|
||||
</Button>
|
||||
<div className="space-x-5">
|
||||
{terminal ? (
|
||||
<Button variant={'destructive'} onClick={killTerminal}>
|
||||
Terminate
|
||||
</Button>
|
||||
) : (
|
||||
<Button variant={'default'} onClick={() => startTerminal(adb)}>
|
||||
Start
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{terminal ? (
|
||||
|
|
@ -114,7 +63,6 @@ const ShellTerminal: React.FC<ShellTerminalProps> = ({ terminal }) => {
|
|||
const shellRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
console.log(shellRef.current)
|
||||
// check if shellRef is have child remove all
|
||||
if (shellRef.current && shellRef.current.children.length > 0) {
|
||||
for (const child of shellRef.current.children) {
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import { Adb, AdbDaemonTransport } from '@yume-chan/adb'
|
|||
import AdbWebCredentialStore from '@yume-chan/adb-credential-web'
|
||||
import {
|
||||
ADB_DEFAULT_DEVICE_FILTER,
|
||||
type AdbDaemonWebUsbDevice,
|
||||
AdbDaemonWebUsbDevice,
|
||||
type AdbDaemonWebUsbDeviceManager
|
||||
} from '@yume-chan/adb-daemon-webusb'
|
||||
import { useState } from 'react'
|
||||
|
|
@ -32,9 +32,11 @@ export const ToolBar: React.FC<ToolBarProps> = ({ manager, adb, device, setAdb,
|
|||
const [version, setVersion] = useState<string>('')
|
||||
|
||||
async function createNewConnection() {
|
||||
let selectedDevice
|
||||
|
||||
console.log(device)
|
||||
let selectedDevice: AdbDaemonWebUsbDevice | undefined = undefined
|
||||
if (!device) {
|
||||
console.log('no device')
|
||||
|
||||
selectedDevice = await manager?.requestDevice({
|
||||
filters: [
|
||||
{
|
||||
|
|
@ -53,9 +55,13 @@ export const ToolBar: React.FC<ToolBarProps> = ({ manager, adb, device, setAdb,
|
|||
selectedDevice = device
|
||||
}
|
||||
|
||||
// create transport and connect to device
|
||||
let adb: Adb | null = null
|
||||
let connection
|
||||
try {
|
||||
connection = await selectedDevice.connect()
|
||||
if (selectedDevice instanceof AdbDaemonWebUsbDevice) {
|
||||
connection = await selectedDevice.connect()
|
||||
}
|
||||
} catch (e) {
|
||||
toast({
|
||||
duration: 5000,
|
||||
|
|
@ -66,24 +72,46 @@ export const ToolBar: React.FC<ToolBarProps> = ({ manager, adb, device, setAdb,
|
|||
return
|
||||
}
|
||||
|
||||
const credentialStore: AdbWebCredentialStore = new AdbWebCredentialStore()
|
||||
if (connection) {
|
||||
const credentialStore: AdbWebCredentialStore = new AdbWebCredentialStore()
|
||||
|
||||
const transport = await AdbDaemonTransport.authenticate({
|
||||
serial: selectedDevice.serial,
|
||||
connection: connection,
|
||||
credentialStore: credentialStore
|
||||
})
|
||||
const transport = await AdbDaemonTransport.authenticate({
|
||||
serial: selectedDevice.serial,
|
||||
connection: connection,
|
||||
credentialStore: credentialStore
|
||||
})
|
||||
|
||||
const adb: Adb = new Adb(transport)
|
||||
adb = new Adb(transport)
|
||||
}
|
||||
|
||||
const name = await adb.getProp('ro.product.model')
|
||||
const version = await adb.getProp('ro.build.version.release')
|
||||
if (adb) {
|
||||
const name = await adb.getProp('ro.product.model')
|
||||
const version = await adb.getProp('ro.build.version.release')
|
||||
|
||||
setName(name)
|
||||
setResolution(resolution)
|
||||
setVersion(version)
|
||||
setName(name)
|
||||
setResolution(resolution)
|
||||
setVersion(version)
|
||||
|
||||
setAdb(adb)
|
||||
setAdb(adb)
|
||||
}
|
||||
}
|
||||
|
||||
async function connectAdbDaemon() {
|
||||
if (!window.electronRuntime) {
|
||||
toast({
|
||||
duration: 5000,
|
||||
variant: 'destructive',
|
||||
title: 'Failed to connect to adb daemon',
|
||||
description: 'This feature is only available in the desktop app'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// create connection
|
||||
await window.ipcRenderer.invoke('adb')
|
||||
|
||||
const result = await window.ipcRenderer.invoke('adb:shell', 'ls')
|
||||
console.log(result)
|
||||
}
|
||||
|
||||
function onDisconnect() {
|
||||
|
|
@ -121,6 +149,10 @@ export const ToolBar: React.FC<ToolBarProps> = ({ manager, adb, device, setAdb,
|
|||
Connect
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<Button variant={'default'} onClick={connectAdbDaemon}>
|
||||
Connect Adb Daemon
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue