Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Interactive Shell Sessions (openInteractiveShell)

The Interactive Shell API opens a persistent bi-directional ADB streaming session. This is designed for terminal emulators (such as xterm.js), interactive command prompts (sh), and continuous background log streamers (logcat -v time -T 25).

Required Permission: "shell"

dioxamine.openInteractiveShell()

Opens a live interactive PTY shell stream to the target device.

Signature

dioxamine.openInteractiveShell(): Promise<InteractiveShellSession>

Parameters

None.

Returns

A Promise resolving to an InteractiveShellSession controller object.


InteractiveShellSession Interface

interface InteractiveShellSession {
    sessionId: string;
    
    // Register incoming binary data callback (data is base64 encoded)
    onData(callback: (base64Chunk: string) => void): void;
    
    // Register stream termination callback
    onClose(callback: (error?: string) => void): void;
    
    // Send binary data into the shell stream (data must be base64 encoded)
    write(base64Data: string): Promise<void>;
    
    // Dynamically resize target device PTY dimensions (triggers SIGWINCH on device)
    resize(cols: number, rows: number): Promise<void>;
    
    // Terminate the shell session and close native socket
    close(): Promise<void>;
}

Dynamic Window Sizing (session.resize)

Interactive full-screen CLI apps (like nano, htop, vim, and top) rely on the Linux kernel PTY dimensions (struct winsize) to layout their interface correctly.

When running inside a responsive WebView, whenever the terminal size changes (such as on screen rotation, split-screen, or when the virtual keyboard appears), call session.resize(cols, rows).

  • cols: Number of columns (positive integer, 1..65535).
  • rows: Number of rows (positive integer, 1..65535).

This sends an official ADB Shell v2 WINDOW_SIZE_CHANGE packet to the Android daemon, which executes ioctl(pty_fd, TIOCSWINSZ) and broadcasts SIGWINCH to the running command.


Encoding and Decoding Data

Data transmitted over onData and write is encoded in Base64 to support arbitrary binary streams, ANSI escape codes, and UTF-8 multibyte characters safely.

You can use the built-in helper functions dioxamine.utf8ToBase64() and dioxamine.base64ToUtf8() or standard Web APIs (TextEncoder/TextDecoder):

// Converting String -> Base64 for session.write()
function stringToBase64(str) {
    const bytes = new TextEncoder().encode(str);
    let bin = '';
    for (let i = 0; i < bytes.length; i++) bin += String.fromCharCode(bytes[i]);
    return btoa(bin);
}

// Converting Base64 -> String from session.onData()
function base64ToString(b64) {
    const bin = atob(b64);
    const bytes = new Uint8Array(bin.length);
    for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
    return new TextDecoder('utf-8', { fatal: false }).decode(bytes);
}

Example 1: Integrating with xterm.js & FitAddon

const term = new Terminal({
    cursorBlink: true,
    fontFamily: 'monospace',
    theme: { background: '#121212', foreground: '#ffffff' }
});

const fitAddon = new FitAddon.FitAddon();
term.loadAddon(fitAddon);

term.open(document.getElementById('terminal-container'));
fitAddon.fit();

async function startTerminal() {
    const session = await dioxamine.openInteractiveShell();

    // Send initial dimensions
    session.resize(term.cols, term.rows);

    // Device stdout -> xterm.js
    session.onData((b64) => {
        const text = dioxamine.base64ToUtf8(b64);
        term.write(text);
    });

    // Keyboard input -> Device stdin
    term.onData((data) => {
        session.write(dioxamine.utf8ToBase64(data));
    });

    // Dynamic resizing (device rotation / soft keyboard toggling)
    term.onResize(({ cols, rows }) => {
        session.resize(cols, rows);
    });

    window.addEventListener('resize', () => {
        fitAddon.fit();
    });

    session.onClose((err) => {
        term.writeln("\r\n[Session Terminated: " + (err || "OK") + "]");
    });
}

Example 2: Continuous Logcat Stream

async function startLiveLogcat(onLineReceived) {
    const session = await dioxamine.openInteractiveShell();
    let buffer = '';

    session.onData((b64) => {
        buffer += dioxamine.base64ToUtf8(b64);
        const lines = buffer.split('\n');
        buffer = lines.pop(); // keep trailing partial line

        for (let i = 0; i < lines.length; i++) {
            const line = lines[i].trimEnd();
            if (line) onLineReceived(line);
        }
    });

    // Send command to follow the last 25 lines continuously (-T)
    const cmd = "logcat -v time -T 25\n";
    await session.write(dioxamine.utf8ToBase64(cmd));
    return session;
}