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.
Github: https://github.com/lxsang/PTerm
Since libc's 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 ssh
.
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.
Metacello new
repository: 'github://lxsang/PTerm';
baseline:'PTerm';
load
Bugs are expected and welcome!