101 lines
3 KiB
TypeScript
101 lines
3 KiB
TypeScript
|
|
import { Button } from '@/components/ui/button'
|
||
|
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
||
|
|
import useAdb from '@/hooks/useAdb'
|
||
|
|
import { encodeUtf8, type AdbSubprocessProtocol } from '@yume-chan/adb'
|
||
|
|
import { Consumable, WritableStream } from '@yume-chan/stream-extra'
|
||
|
|
import { useEffect, useRef, useState } from 'react'
|
||
|
|
|
||
|
|
import { Terminal } from 'xterm'
|
||
|
|
import { FitAddon } from 'xterm-addon-fit'
|
||
|
|
|
||
|
|
import 'xterm/css/xterm.css'
|
||
|
|
|
||
|
|
export const ShellTab: React.FC = () => {
|
||
|
|
const adb = useAdb(state => state.adb)
|
||
|
|
|
||
|
|
const shellRef = useRef<HTMLDivElement>(null)
|
||
|
|
|
||
|
|
const [process, setProcess] = useState<AdbSubprocessProtocol | undefined>()
|
||
|
|
|
||
|
|
useEffect(() => {
|
||
|
|
const startTerminal = async () => {
|
||
|
|
if (shellRef.current && adb) {
|
||
|
|
if (shellRef.current.children.length > 0) {
|
||
|
|
// remove all children from the shellRef
|
||
|
|
while (shellRef.current.firstChild) {
|
||
|
|
shellRef.current.removeChild(shellRef.current.firstChild)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
const terminal: Terminal = new Terminal()
|
||
|
|
const fitAddon = new FitAddon()
|
||
|
|
terminal.loadAddon(fitAddon)
|
||
|
|
|
||
|
|
const process: AdbSubprocessProtocol = await adb.subprocess.shell(
|
||
|
|
'/data/data/com.termux/files/usr/bin/telnet localhost 45515'
|
||
|
|
)
|
||
|
|
process.stdout.pipeTo(
|
||
|
|
new WritableStream<Uint8Array>({
|
||
|
|
write(chunk) {
|
||
|
|
terminal.write(chunk)
|
||
|
|
}
|
||
|
|
})
|
||
|
|
)
|
||
|
|
|
||
|
|
const writer = process.stdin.getWriter()
|
||
|
|
terminal.onData(data => {
|
||
|
|
const buffer = encodeUtf8(data)
|
||
|
|
const consumable = new Consumable(buffer)
|
||
|
|
writer.write(consumable)
|
||
|
|
})
|
||
|
|
|
||
|
|
terminal.options.cursorBlink = true
|
||
|
|
terminal.options.theme = {
|
||
|
|
background: '#1e1e1e',
|
||
|
|
foreground: '#d4d4d4'
|
||
|
|
}
|
||
|
|
|
||
|
|
terminal.open(shellRef.current)
|
||
|
|
fitAddon.fit()
|
||
|
|
setProcess(process)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
startTerminal()
|
||
|
|
|
||
|
|
return () => {
|
||
|
|
console.log('cleaning up shell')
|
||
|
|
shellRef.current && shellRef.current.firstChild && shellRef.current.removeChild(shellRef.current.firstChild)
|
||
|
|
process?.stderr.cancel()
|
||
|
|
process?.stdin.close()
|
||
|
|
process?.stdout.cancel()
|
||
|
|
process?.kill()
|
||
|
|
}
|
||
|
|
}, [shellRef, adb])
|
||
|
|
|
||
|
|
return (
|
||
|
|
<Card>
|
||
|
|
<CardHeader>
|
||
|
|
<CardTitle>Shell</CardTitle>
|
||
|
|
<CardDescription>Access your device's shell using a terminal emulator</CardDescription>
|
||
|
|
</CardHeader>
|
||
|
|
<CardContent>
|
||
|
|
<div className="flex items-center justify-end py-3 w-full">
|
||
|
|
<div>
|
||
|
|
<Button
|
||
|
|
variant={'destructive'}
|
||
|
|
onClick={() => {
|
||
|
|
process?.stderr.cancel()
|
||
|
|
process?.stdin.close()
|
||
|
|
process?.stdout.cancel()
|
||
|
|
process?.kill()
|
||
|
|
}}
|
||
|
|
>
|
||
|
|
Kill Shell
|
||
|
|
</Button>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<div className="w-full h-[800px] bg-slate-700" ref={shellRef}></div>
|
||
|
|
</CardContent>
|
||
|
|
</Card>
|
||
|
|
)
|
||
|
|
}
|