I use Unix terminal a lot in work, when i work with Pharo and ROS (PhaROS), switching regularly between Pharo and native terminal application (for ROS command line) is kind of inconvenient. I've been thinking of using a terminal emulator application for Pharo. Googling around, i found out that there is no such thing that is ready for production work on modern Pharo, except a prototype work of Pavel Krivanek available at: https://github.com/pavel-krivanek/terminal. However, that code is messy, buggy, and not ready for production work . So i decided to take my time to work on it.
I grabbed the code from Pavel's repository, kept and improved only the UI and protocol parts (fix display/keyboard event bugs, clean up unused code, etc.), then decided to implement my own FFI calls to the underlying system terminal.
fork() does not work really well on Pharo, an in-image (no external C lib calls other than libc) solution to spawn a tty using classic
fork() is not feasible in this case. An alternative way is to use
posix_spawn(), this function allows to spawn a child process and execute an Unix command on that process (eg. /bin/bash). With
posix_spawn and some setting functions (for IO redirection), i am able to spawn a Bash on a new child process and redirect its IO to Pharo using only in-image FFI calls to libC (no external C code needed). However, due to the limitation of
posix_spawn we cannot control what happens inside the child process, including making the child process a new session leader (using
setsid()). Consequently, the spawned tty is not fully interactive, the following warning will display:
The spawned shell works well for almost single process commands like
ls, top, htop, etc, but does not support commands that require job control or command that creates new subprocess like
Since i really need a fully interactive shell, the classic way is to use
fork() in an custom C library for spawning a tty in a child process, then make FFI calls to that library in Pharo whenever we want the terminal access. My solution in this case is similar to what Pavel did. However, the C code is a little bit different. The down side is that this solution requires
gcc to be installed on the system. This allows to compile the custom C code to shared library on the first run of the application. The compilation is automatic and off-scene without user notice though.
The idea is that we can use the in-image solution as a fallback situation for the custom C library solution. On the first run, the application will try to compile the C code and make FFI calls to the compiled lib in order to spawn a fully interactive shell. For some reason, if the application fail to access or compile the C lib, a second attempt will be made by using the in-image FFI calls, if success, a shell without job control will be spawned.
Bugs are expected and welcome!