TerminalStudio / flutter_pty

Pty for Flutter. Provides the ability to create processes with pseudo terminal file descriptors.
https://pub.dev/packages/flutter_pty
MIT License
24 stars 21 forks source link

Can't get input to render using xterm + flutter_pty #4

Closed holdenhinkle closed 2 years ago

holdenhinkle commented 2 years ago

Hi there,

I was excited to discover xterm and flutter_pty today. Thanks for all of your work.

I'm trying to connect xterm and flutter_pty and I'm having a difficult time getting the input to render to the screen. I'm guessing I haven't implemented LocalTerminalBackend#out correctly.

Also, wherever I try to call pty.output.listen I get the following error:

Exception has occurred.
StateError (Bad state: Stream has already been listened to.)

Here's my code:

import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:flutter_pty/flutter_pty.dart';
import 'package:xterm/flutter.dart';
import 'package:xterm/xterm.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      title: 'xterm / flutter_pty demo',
      home: LocalTerminal(),
    );
  }
}

class LocalTerminalBackend implements TerminalBackend {
  LocalTerminalBackend();

  final pty = Pty.start('bash');

  @override
  void init() {
    // NOOP
  }

  @override
  void write(String input) => pty.write(const Utf8Encoder().convert(input));

  @override
  Stream<String> get out =>
      pty.output.map((data) => String.fromCharCodes(data));

  @override
  void resize(int width, int height, int pixelWidth, int pixelHeight) =>
      pty.resize(width, height);

  @override
  void ackProcessed() {
    // NOOP
  }

  @override
  Future<int> get exitCode => pty.exitCode;

  @override
  void terminate() => pty.kill();
}

class LocalTerminal extends StatefulWidget {
  const LocalTerminal({Key? key}) : super(key: key);

  @override
  _LocalTerminalState createState() => _LocalTerminalState();
}

class _LocalTerminalState extends State<LocalTerminal> {
  late Terminal terminal;

  @override
  void initState() {
    super.initState();
    terminal = Terminal(maxLines: 10000, backend: LocalTerminalBackend());
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: TerminalView(
          terminal: terminal,
        ),
      ),
    );
  }
}

I'm sure I'm doing something wrong. Any help getting this working is greatly appreciated. Thanks!

holdenhinkle commented 2 years ago

I updated LocalTerminalBackend#out to:

  @override
  Stream<String> get out => pty.output.map((data) => utf8.decode(data));

But that didn't work.

Ah, I see why I was getting that StateError (Bad state: Stream has already been listened to.) error. It's because it's already been listened to in Terminal:

backend?.out.listen(write);

Yeah, I'm missing something...