preface

  • This paper mainly introduces the open source terminal simulator series.
  • The code is only for exchange and learning, and all the code is open source under a loose protocol. The code was carefully introduced into the formal project during extensive testing.

Open source list

  • dart_pty
  • termare_ssh
  • termare_pty
  • termare_view

dart_pty

Introduction to the

Create a dummy terminal and execute a child process. It is also the underlying implementation of the terminal emulator. Unlike the execution process provided by the language, it can not only take stdout and STderr output without buffering, but also take all terminal sequences and interact with the process.

The cause of

Functions to create child processes are provided in various languages, but none of these functions can get the output of the child process without buffering, nor can they interact with the child process at runtime. For example, run in terminal emulator

python
Copy the code

So it should output

Python 3.8.6 (default, Oct  8 2020, 14:06:32) 
[Clang 12.0.0 (clang-1200.0.32.2)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> 
Copy the code

In Java we use:

Runtime.getRuntime().exec("python");
Copy the code

Python:

system("python");
Copy the code

C:

system("python");
Copy the code

In the dart

Process.start("python");
Copy the code

Process creation, we are not able to interact with the child, and whether it is a kind of language, we can’t execute python take like terminal output, this is because the buffer is not full 4096 bytes (usually), the above mentioned function to obtain the output of the child process is described in the form of a pipeline, and to capture the output in the pipeline is full buffer, The full buffer is 4096 bytes and the line buffer is 1024 bytes on the terminal, and we can’t get its output without the process actively flushing the buffer.

C is compiled to binary and can interact because the child of the System function has the same input and output as the main process.

The principle of analytic

Principle of parsing walks article: Flutter terminal emulator to explore (2) of article | complete terminal emulator

It’s too much to be the focus of this article.

Begin to use

This is a pure DART project that you can integrate into either dart project or FLUTTER project.

Yaml configuration

  dart_pty:
    git: https://github.com/termare/dart_pty
Copy the code

Dart Package has been uploaded. The code on dart Package may vary greatly before release 1.0.

Import packages

import 'package:dart_pty/src/unix_pty_c.dart';
Copy the code

Create a PTY object

  Map<String.String> environment = {'TEST': 'TEST_VALUE'};
  UnixPtyC unixPthC = UnixPtyC(
    environment: environment,
    libPath: 'dynamic_library/libterm.dylib',);Copy the code

When the object is created, there is already a file descriptor that reads and writes to the same process. By writing to the file descriptor, you can interact with the child process, and by reading, you can get the child process’s output, which is unbuffered.

Read and write child process

  await Future.delayed(Duration(milliseconds: 100), () async {
    while (true) {
      print('Please enter something into the terminal.');
      String input = stdin.readLineSync();
      unixPthC.write(input + '\n');
      await Future.delayed(Duration(milliseconds: 200));
      result = unixPthC.read();
      print('\x1b[31m' + The '-' * 20 + 'result' + The '-' * 20);
      print('result -> $result');
      print(The '-' * 20 + 'result' + The '-' * 20 + '\x1b[0m');
      await Future.delayed(Duration(milliseconds: 100)); }});Copy the code

disadvantages

  • The so library needs to be introduced.
  • Windows is not supported yet.

Can local terminals be implemented without integrating the SO library?

Dart: FFI is used to write the original underlying logic, so that it can completely break away from THE C language part, no longer need to compile a local library for each platform, which was actually implemented by swJTU in the summer vacation of 20. He used isolate to solve the problem that reading file descriptors would block UI threads, but the isolate was not hot-loaded and vscode would display a bunch of isolate runtime when there were too many terminals.

I wanted to do this by setting the file descriptor to non-blocking. The terminal was created and the child forked without a problem, but when I executed the following code, I found a difference. If the O_NONBLOCK macro is found on the android device, it should be related to the macro definition of flag on the device. If you can find the macro definition value of O_NONBLOCK on Android, it can be solved.

Dart: FFI can crash on Android when using header files.

Here is the dart code:

  void setNonblock(int fd, {bool verbose = false{})int flag = - 1;
    flag = cfcntl.fcntl(fd, F_GETFL, 0); // Get the current flag
    if (verbose) print('>>>>>>>> Current flag =$flag');
    flag |= O_NONBLOCK; // Set the new falg
    if (verbose) print('>>>>>>>> set new flag =$flag');
    cfcntl.fcntl(fd, F_SETFL, flag); / / update the flag
    flag = cfcntl.fcntl(fd, F_GETFL, 0); // Get the current flag
    if (verbose) print('>>>>>>>> Obtained flag = again$flag');
  }
Copy the code

The dart code is written in C as follows:

void setNonblock(int fd)
{
    int flag = - 1;
    flag = fcntl(fd, F_GETFL); // Get the current flag
    flag |= O_NONBLOCK;        // Set the new falg
    fcntl(fd, F_SETFL, flag);  / / update the flag
}
Copy the code
  • Unix_pty: ffI implementation.
  • Term.c: low-level implementation.

On the dart: use ffi can be found in the dart ffi explore new train of thought (3) | use ffi

termare_view

Supports full platform terminal emulators developed using Flutter, independent of platform code.

This is the “Flutter terminal emulator to explore (3) | principle analysis and integration” of the article after exploration, continuously develop code maintenance warehouse.

If you want a more stable flutter terminal emulator, you can use the xterm. The dart, the development of southwest jiaotong university leaders mentioned above, I choose their own development is more adaptive to the mobile was the main reason for a performance, and to this end spent a lot of energy, his project terminal component is handled through oneself want to write.

Background analysis

I needed to maintain my own terminal emulator, which needed to be integrated into mobile applications or desktop applications. At first, I considered using Android-terminal-Emulator, but later, because the terminal emulator had not been maintained for a long time, I switched to Termux. Termux is very rich and open source throughout the organization, but if I integrate it entirely with the Termux code into a personal project, on the Android SIDE I can jump activities and send broadcast notifications via the AM command to the native terminal emulator to automatically type commands, but on the desktop side I can’t.

The idea arises. What if I rewrite the simulator with Flutter? It is a beautiful idea to write once and run everywhere, but it is really difficult for me. The whole process is rather bumpy. With the continuous learning of technology, many implementations are gradually approaching a standardized way.

My first thought was to read the source code of Termux, with few comments and very difficult to understand. There were many holes in the custom source on the mobile terminal, so I wanted to consult someone who knew the whole development process. First of all, the author of Termux must know, and then the developer of Neoterm, who was only a senior three when I knew her. The terminal simulator Neoterm has been put on the shelf, with 11W downloads at present. Therefore, both she and the author of Xterm.dart have helped me very well in the whole development.

Major mistakes in previous development

  • I should not have combined the terminal emulator’s upper rendering component with the local underlying onepseudo terminalForced integration, referencexterm.jswithxterm.dartA terminal should be an independent, upper-layer component that can receive any input stream, not only from a local terminal, but also from SSH, or from anywhere else.
  • It should not be written blindly without reference to existing implementations of similar componentsWidgetSpanThis component.

After the reconstruction

  • Draw the entire terminal emulator using canvas.
  • The content of the terminal input stream is parsed into a two-dimensional array according to the terminal sequenceCustomPainterInternally draws content and cursor based on a two-dimensional array.

Begin to use

The introduction of the project

This is a pure flutter package, so you only need to introduce it under the dependencies of the YAML configuration file:

termare_view:
  git: https://github.com/termare/termare_view
Copy the code

Creating a Terminal Controller

TermareController controller = TermareController(
  showBackgroundLine: true,);Copy the code

The showBackgroundLine property enables the debug grid background.

Using the component

TermareView(
  controller: controller,
),
Copy the code

Let the terminal display something

controller.write('hello termare_view');
Copy the code

Run as follows:

You might notice, isn’t it just drawing a grid background and then drawing text?

To perform:

controller.write('\x1B[1;31mhello termare_view\x1B[0m\n');
controller.write('\x1B[1;32mhello termare_view\x1B[0m\n');
Copy the code

So the question of what a terminal sequence is is also illustrated by this example, in which the output stream from any terminal is informed of the simulator’s drawing behavior and other behavior by a sequence like “\x1B[1;31m”.

As shown in the example.

Dart print ‘\x1B[1;31mhello termare_view\x1B[0m\n’ also works.

Terminal sequence list

From the xterm. Js.

termare_pty

This is an example of using PTY to implement a local terminal. Rely on dart_pty and termare_view.

The principle of analytic

The first open source library, Dart_pty, implements a local terminal and binds the input and output streams of the terminal to termare_VIEW.

Create pty

    unixPtyC = widget.unixPtyC ??
        UnixPtyC(
          libPath: Platform.isMacOS
              ? '/Users/nightmare/Desktop/termare-space/dart_pty/dynamic_library/libterm.dylib'
              : 'libterm.so',
          rowLen: row,
          columnLen: column - 2,
          environment: <String.String> {'TERM': 'screen-256color'.'PATH':
                '/data/data/com.nightmare/files/usr/bin:${Platform.environment['PATH']}',});Copy the code

Dart_pty contains Linux, MacOS compiled libraries, termare_pty contains android so libraries.

Bind to terminal components

    while (mounted) {
      final String cur = unixPtyC.read();
      if (cur.isNotEmpty) {
        controller.write(cur);
        controller.autoScroll = true;
        controller.notifyListeners();
        await Future<void>.delayed(const Duration(milliseconds: 10));
      } else {
        await Future<void>.delayed(const Duration(milliseconds: 100)); }}Copy the code

This code should be easy to understand. The controller is TermareController.

According to

  Widget build(BuildContext context) {
    return TermareView(
      keyboardInput: (String data) {
        unixPtyC.write(data);
      },
      controller: controller,
    );
  }
Copy the code

Run a screenshot

I think the screenshots are a little big,

See the sample code termare_pty.dart.

termare_ssh

This is an example of using SSH to connect to a server terminal, or localhost.

The principle of analytic

Get an output stream connected to the server using dartSSH, the pure DART package, and bind the input and output to termare_view.

Connecting to the server

void connect() {
    controller.write('connecting ${widget.hostName}. \n');
    client = SSHClient(
      hostport: Uri.parse('ssh://' + widget.hostName + '22'.),
      login: widget.loginName,
      print: print,
      termWidth: 80,
      termHeight: 25,
      termvar: 'xterm-256color',
      getPassword: () => Uint8List.fromList(utf8.encode(widget.password)),
      response: (SSHTransport transport, String data) {
        controller.write(data);
      },
      success: () {
        controller.write('connected.\n');
      },
      disconnected: () {
        controller.write('disconnected.'); });Copy the code

The controller is TermareController.

According to

  Widget build(BuildContext context) {
    return TermareView(
        controller: controller,
        keyboardInput: (Stringdata) { client? .sendChannelData(Uint8List.fromList(utf8.encode(data))); }); }Copy the code

Run a screenshot

Android

Linux

Macos

Windows

Ios

See the sample code termare_ssh.dart.

conclusion

Open source for exchange learning only, exchange learning.

If you are interested in terminal emulators, you are welcome to contribute to these repositories with me and feel free to talk to me. If you have any questions about these, please leave a comment in the comments section.