Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Unverified Commit c3a61f9b authored by Simon Chan's avatar Simon Chan
Browse files

feat(bin): logcat binary format parsing

ref #359
parent f46ff368
Loading
Loading
Loading
Loading
+5 −2
Original line number Diff line number Diff line
@@ -162,6 +162,9 @@ function _Connect(): JSX.Element | null {
                    device = await Adb.authenticate({ readable, writable }, CredentialStore, undefined);
                    device.disconnected.then(() => {
                        globalState.setDevice(undefined, undefined);
                    }, (e) => {
                        globalState.showErrorDialog(e);
                        globalState.setDevice(undefined, undefined);
                    });
                    globalState.setDevice(selectedBackend, device);
                } catch (e) {
@@ -170,7 +173,7 @@ function _Connect(): JSX.Element | null {
                }
            }
        } catch (e: any) {
            globalState.showErrorDialog(e.message);
            globalState.showErrorDialog(e);
        } finally {
            setConnecting(false);
        }
@@ -180,7 +183,7 @@ function _Connect(): JSX.Element | null {
            await globalState.device!.dispose();
            globalState.setDevice(undefined, undefined);
        } catch (e: any) {
            globalState.showErrorDialog(e.message);
            globalState.showErrorDialog(e);
        }
    }, []);

+6 −6
Original line number Diff line number Diff line
@@ -140,8 +140,8 @@ class FileManagerState {
                                    const itemPath = path.resolve(this.path, item.name);
                                    await sync.read(itemPath)
                                        .pipeTo(saveFile(item.name, Number(item.size)));
                                } catch (e) {
                                    globalState.showErrorDialog(e instanceof Error ? e.message : `${e}`);
                                } catch (e: any) {
                                    globalState.showErrorDialog(e);
                                } finally {
                                    sync.dispose();
                                }
@@ -169,8 +169,8 @@ class FileManagerState {
                                        return;
                                    }
                                }
                            } catch (e) {
                                globalState.showErrorDialog(e instanceof Error ? e.message : `${e}`);
                            } catch (e: any) {
                                globalState.showErrorDialog(e);
                            } finally {
                                this.loadFiles();
                            }
@@ -481,8 +481,8 @@ class FileManagerState {
            } finally {
                clearInterval(intervalId);
            }
        } catch (e) {
            globalState.showErrorDialog(e instanceof Error ? e.message : `${e}`);
        } catch (e: any) {
            globalState.showErrorDialog(e);
        } finally {
            sync.dispose();
            this.loadFiles();
+2 −2
Original line number Diff line number Diff line
@@ -45,8 +45,8 @@ const FrameBuffer: NextPage = (): JSX.Element | null => {
        try {
            const framebuffer = await globalState.device.framebuffer();
            state.setImage(framebuffer);
        } catch (e) {
            globalState.showErrorDialog(e instanceof Error ? e.message : `${e}`);
        } catch (e: any) {
            globalState.showErrorDialog(e);
        }
    }, []);

+301 −0
Original line number Diff line number Diff line
// cspell: ignore logcat

import { ICommandBarItemProps, Stack, StackItem } from "@fluentui/react";
import { makeStyles, mergeClasses, shorthands } from "@griffel/react";
import { AbortController, decodeUtf8, ReadableStream, WritableStream } from '@yume-chan/adb';
import { Logcat, LogMessage, LogPriority } from '@yume-chan/android-bin';
import { autorun, makeAutoObservable, observable, runInAction } from "mobx";
import { observer } from "mobx-react-lite";
import { NextPage } from "next";
import Head from "next/head";
import { CommandBar, Grid, GridColumn, GridHeaderProps, GridRowProps } from "../components";
import { globalState } from "../state";
import { Icons, RouteStackProps, useCallbackRef } from "../utils";

const LINE_HEIGHT = 32;

const useClasses = makeStyles({
    grid: {
        height: '100%',
        marginLeft: '-16px',
        marginRight: '-16px',
    },
    header: {
        textAlign: 'center',
        lineHeight: `${LINE_HEIGHT}px`,
    },
    row: {
        '&:hover': {
            backgroundColor: '#f3f2f1',
        },
    },
    selected: {
        backgroundColor: '#edebe9',
    },
    code: {
        fontFamily: 'monospace',
        textOverflow: 'ellipsis',
        whiteSpace: 'nowrap',
        lineHeight: LINE_HEIGHT + 'px',
        cursor: 'default',
        ...shorthands.overflow('hidden'),
    },
});

export interface Column extends GridColumn {
    title: string;
}

export interface LogRow extends LogMessage {
    timeString?: string;
    payloadString?: string;
}

const state = makeAutoObservable({
    logcat: undefined as Logcat | undefined,
    running: false,
    list: [] as LogRow[],
    stream: undefined as ReadableStream<LogMessage> | undefined,
    stopSignal: undefined as AbortController | undefined,
    selectedCount: 0,
    start() {
        if (this.running) {
            return;
        }

        this.running = true;
        this.stream = this.logcat!.binary();
        this.stopSignal = new AbortController();
        this.stream
            .pipeTo(
                new WritableStream({
                    write: (chunk) => {
                        runInAction(() => {
                            this.list.push(chunk);
                        });
                    },
                }),
                { signal: this.stopSignal.signal }
            )
            .catch(() => { });
    },
    stop() {
        this.running = false;
        this.stopSignal!.abort();
    },
    clear() {
        this.list = [];
        this.selectedCount = 0;
    },
    get empty() {
        return this.list.length === 0;
    },
    get commandBar(): ICommandBarItemProps[] {
        return [
            this.running ? {
                key: "stop",
                text: "Stop",
                iconProps: { iconName: Icons.Stop },
                onClick: () => this.stop(),
            } : {
                key: "start",
                text: "Start",
                disabled: this.logcat === undefined,
                iconProps: { iconName: Icons.Play },
                onClick: () => this.start(),
            },
            {
                key: 'clear',
                text: 'Clear',
                disabled: this.empty,
                iconProps: { iconName: Icons.Delete },
                onClick: () => this.clear(),
            },
            {
                key: 'copyAll',
                text: 'Copy Rows',
                disabled: this.selectedCount === 0,
                iconProps: { iconName: Icons.Copy },
                onClick: () => {

                }
            },
            {
                key: 'copyText',
                text: 'Copy Messages',
                disabled: this.selectedCount === 0,
                iconProps: { iconName: Icons.Copy },
                onClick: () => {

                }
            }
        ];
    },
    get columns(): Column[] {
        return [
            {
                width: 200,
                title: 'Time',
                CellComponent: ({ rowIndex, columnIndex, className, ...rest }) => {
                    const item = this.list[rowIndex];
                    if (!item.timeString) {
                        item.timeString = new Date(item.second * 1000).toISOString();
                    }

                    const classes = useClasses();

                    return (
                        <div className={mergeClasses(classes.code, className)} {...rest}>
                            {item.timeString}
                        </div>
                    );
                }
            },
            {
                width: 80,
                title: 'PID',
                CellComponent: ({ rowIndex, columnIndex, className, ...rest }) => {
                    const item = this.list[rowIndex];

                    const classes = useClasses();

                    return (
                        <div className={mergeClasses(classes.code, className)} {...rest}>
                            {item.pid}
                        </div>
                    );
                }
            },
            {
                width: 80,
                title: 'TID',
                CellComponent: ({ rowIndex, columnIndex, className, ...rest }) => {
                    const item = this.list[rowIndex];

                    const classes = useClasses();

                    return (
                        <div className={mergeClasses(classes.code, className)} {...rest}>
                            {item.tid}
                        </div>
                    );
                }
            },
            {
                width: 100,
                title: 'Priority',
                CellComponent: ({ rowIndex, columnIndex, className, ...rest }) => {
                    const item = this.list[rowIndex];

                    const classes = useClasses();

                    return (
                        <div className={mergeClasses(classes.code, className)} {...rest}>
                            {LogPriority[item.priority]}
                        </div>
                    );
                }
            },
            {
                width: 300,
                flexGrow: 1,
                title: 'Payload',
                CellComponent: ({ rowIndex, columnIndex, className, ...rest }) => {
                    const item = this.list[rowIndex];
                    if (!item.payloadString) {
                        item.payloadString = decodeUtf8(item.payload);
                    }

                    const classes = useClasses();

                    return (
                        <div className={mergeClasses(classes.code, className)} {...rest}>
                            {item.payloadString}
                        </div>
                    );
                }
            },
        ];
    },
}, {
    list: observable.shallow,
});

console.log(state);

autorun(() => {
    if (globalState.device) {
        state.logcat = new Logcat(globalState.device);
    } else {
        state.logcat = undefined;
        if (state.running) {
            state.stop();
        }
    }
});

const Header = observer(function Header({
    className,
    columnIndex,
    ...rest
}: GridHeaderProps) {
    const classes = useClasses();

    return (
        <div className={mergeClasses(className, classes.header)} {...rest}>
            {state.columns[columnIndex].title}
        </div>
    );
});

const Row = observer(function Row({
    className,
    rowIndex,
    ...rest
}: GridRowProps) {
    const item = state.list[rowIndex];
    const classes = useClasses();

    const handleClick = useCallbackRef(() => {
        runInAction(() => {
        });
    });

    return (
        <div
            className={mergeClasses(
                className,
                classes.row,
            )}
            onClick={handleClick}
            {...rest}
        />
    );
});

const LogcatPage: NextPage = () => {
    const classes = useClasses();

    return (
        <Stack {...RouteStackProps}>
            <Head>
                <title>Logcat - Android Web Toolbox</title>
            </Head>

            <CommandBar items={state.commandBar} />

            <StackItem grow>
                <Grid
                    className={classes.grid}
                    rowCount={state.list.length}
                    rowHeight={LINE_HEIGHT}
                    columns={state.columns}
                    HeaderComponent={Header}
                    RowComponent={Row}
                />
            </StackItem>
        </Stack>
    );
};

export default observer(LogcatPage);
+141 −140
Original line number Diff line number Diff line
@@ -5,7 +5,6 @@ import { autorun, makeAutoObservable, observable, runInAction } from "mobx";
import { observer } from "mobx-react-lite";
import { NextPage } from "next";
import Head from "next/head";
import { useMemo } from "react";
import { CommandBar, Grid, GridCellProps, GridColumn, GridHeaderProps, GridRowProps, HexViewer, toText } from "../components";
import { globalState, PacketLogItem } from "../state";
import { Icons, RouteStackProps, useCallbackRef, withDisplayName } from "../utils";
@@ -89,17 +88,15 @@ const useClasses = makeStyles({
    },
});

const PacketLog: NextPage = () => {
    const classes = useClasses();

    const columns: Column[] = useMemo(() => [
const columns: Column[] = [
    {
            key: 'direction',
        title: 'Direction',
        width: 100,
        CellComponent: withDisplayName('Direction')(({ className, rowIndex, ...rest }: GridCellProps) => {
            const item = globalState.logs[rowIndex];

            const classes = useClasses();

            return (
                <div
                    className={mergeClasses(className, classes.code)}
@@ -111,7 +108,6 @@ const PacketLog: NextPage = () => {
        }),
    },
    {
            key: 'command',
        title: 'Command',
        width: 100,
        CellComponent: withDisplayName('Command')(({ className, rowIndex, ...rest }: GridCellProps) => {
@@ -123,6 +119,8 @@ const PacketLog: NextPage = () => {
                    decodeUtf8(new Uint32Array([item.command]));
            }

            const classes = useClasses();

            return (
                <div
                    className={mergeClasses(className, classes.code)}
@@ -134,7 +132,6 @@ const PacketLog: NextPage = () => {
        }),
    },
    {
            key: 'arg0',
        title: 'Arg0',
        width: 100,
        CellComponent: withDisplayName('Command')(({ className, rowIndex, ...rest }: GridCellProps) => {
@@ -144,6 +141,8 @@ const PacketLog: NextPage = () => {
                item.arg0String = item.arg0.toString(16).padStart(8, '0');
            }

            const classes = useClasses();

            return (
                <div
                    className={mergeClasses(className, classes.code)}
@@ -155,7 +154,6 @@ const PacketLog: NextPage = () => {
        }),
    },
    {
            key: 'arg1',
        title: 'Arg1',
        width: 100,
        CellComponent: withDisplayName('Command')(({ className, rowIndex, ...rest }: GridCellProps) => {
@@ -165,6 +163,8 @@ const PacketLog: NextPage = () => {
                item.arg1String = item.arg0.toString(16).padStart(8, '0');
            }

            const classes = useClasses();

            return (
                <div
                    className={mergeClasses(className, classes.code)}
@@ -176,7 +176,6 @@ const PacketLog: NextPage = () => {
        }),
    },
    {
            key: 'payload',
        title: 'Payload',
        width: 200,
        flexGrow: 1,
@@ -187,6 +186,8 @@ const PacketLog: NextPage = () => {
                item.payloadString = toText(item.payload.subarray(0, 100));
            }

            const classes = useClasses();

            return (
                <div
                    className={mergeClasses(className, classes.code)}
@@ -197,30 +198,29 @@ const PacketLog: NextPage = () => {
            );
        }),
    },
    ], [classes.code]);
];

    const Header = useMemo(
        () => withDisplayName('Header')(({
const Header = withDisplayName('Header')(({
    className,
    columnIndex,
    ...rest
}: GridHeaderProps) => {
    const classes = useClasses();

    return (
        <div className={mergeClasses(className, classes.header)} {...rest}>
            {columns[columnIndex].title}
        </div>
    );
        }),
        [classes.header, columns]
    );
});

    const Row = useMemo(
        () => observer(function Row({
const Row = observer(function Row({
    className,
    rowIndex,
    ...rest
}: GridRowProps) {
            /* eslint-disable-next-line */
    const classes = useClasses();

    const handleClick = useCallbackRef(() => {
        runInAction(() => {
            state.selectedPacket = globalState.logs[rowIndex];
@@ -238,9 +238,10 @@ const PacketLog: NextPage = () => {
            {...rest}
        />
    );
        }),
        [classes]
    );
});

const PacketLog: NextPage = () => {
    const classes = useClasses();

    return (
        <Stack {...RouteStackProps} tokens={{}}>
Loading