Since 2015 I do most of my software development on an Android tablet. I am a bit fed up with PCs and notebooks, as they are tedious to handle and have too many mechanical parts. A tablet is more convenient, it can be held in one hand, and usually survives if coffee is spilt over it.

The obvious drawbacks of a tablet are a smaller screen size and the lack of a keyboard, but I can compensate these deficits mostly, and want to demonstrate this in the following.


My preferred working style uses multiple terminal windows, some for editing with Vi, some running Bash, and some running PicoLisp. On a desktop these are xterms in an fvwm window manager.

On Android I use Termux with the PentiKeyboard. Termux is an Android terminal emulator and Linux environment, and Penti is a chorded keyboard for Android.

Termux can be found in stores like Google play or F-Droid.org. Inside Termux windows I use the Tmux terminal multiplexer, both locally and on ssh'd remote machines.

Starting Termux automatically also starts a first Tmux window, here indicated by the yellow status line at the bottom:

The white dotted circles are the Penti "keyboard". It is described and downloadable at https://software-lab.de/penti.html.

The Penti Keyboard

Penti has many advantages over the common type of virtual keyboards for touch screens, as it doesn't take up precious screen space, and supports all keys of a standard PC keyboard including punctuation and control characters, navigation and function keys etc., with just one or two touches. There are even "keys" for cut and paste, reset, restart and hide the whole keyboard, and direct numeric input for any unicode character. The Japanese version also supports Kanji input.

Penti distinguishes between "chords" (keys are pressed simultaneously) and "arpeggio" (keys are pressed one after the other).

For example, pressing thumb and index finger together gives the letter "f", but pressing the thumb and then the index finger (while keeping the thumb down) means "shift", indicated by a large letter "S" above the circles.

Pressing index finger and thumb in reverse order, gives a shift lock, and doing the same again releases the lock. Other arpeggio combinations result in punctuation, digits, control, alt and function keys.

Special arpeggios are reserved for Backspace, Tab, Enter and Escape. And — for that matter — Ctrl-Z (suspend key), Ctrl-B (used a lot in Tmux) and Ctrl-Y.

The sixth key in the middle is the repeat key. It repeats the last key that was typed, and does auto-repeat when pressed long enough. If pressed as arpeggio after the thumb, it repeats the one-before-last key.

If the screen is pressed outside any circle, Penti is temporarily hidden and further touches go down to the underlying screen. F-Q (Function-Quit) closes Penti, it usually pops up again when any editable field on the screen is touched.

Important to remember is the function key combination F-H for "Help". It displays a help screen and is closed by any other key press.

Termux and Tmux

When the Bash in Termux starts up, it reads the .profile initiation file, which in turn checks whether it is the first instance, and if so, starts Tmux.

Navigating Termux and Tmux Windows

Termux reads its initiation file .termux/termux.properties. This defines three control key sequences for navigation:
   shortcut.create-session = Ctrl + O
   shortcut.previous-session = Ctrl + P
   shortcut.next-session = Ctrl + N
i.e. Ctrl-O for "open new terminal", Ctrl-P for "previous" and Ctrl-N for next terminal.

Usually I have three Termux windows open, so I can get from any window to any other with a single keystroke ("previous" and "next" move cyclically).

As we noted above, Tmux is started from .profile automatically in the first Termux window. Tmux reads its configuration file .tmux.conf. By default, Tmux navigates in a similar way, with Ctrl-B + p for "previous" and Ctrl-B + n for "next", so we add the definition
   bind-key o new-window
for "open new window".

The yellow status line at the bottom results from
   set -g status-style "bg=yellow,fg=black"
I use colors on different machines.

With Ctrl-B + s we can split a Tmux window into two panes.
   bind-key s split-window -c '#{pane_current_path}'
When opening 3 Termux windows, each with 3 Tmux windows split into 2 panes each, we have 18 Bash terminal sessions open. Each of them can be seen with maximally four keystrokes (e.g. Ctrl, N, Ctrl-B, n), and perhaps two additional keystrokes (Ctrl-B, j) to set the focus to the other pane. Hopefully, the colors in the status bar help not to get fully lost ;)


PicoLisp is available as a Termux package:

You can use it to develop and test normal PicoLisp applications.

Application Server

Let's take the "app/" demo that comes with the PicoLisp distribution.

I don't want to clobber the installation directory, so I put a symbolic link into my Termux home directory:
   :~ ln -s /data/data/com.termux/files/usr/lib/picolisp/app
Then the demo application can be started (it will create a db/ directory in the current working directory):
   :~ pil app/main.l @lib/too.l -main -go +
As always, connect your browser to the default port 8080, and run the application:

The application can now be fully examined and debugged in the terminal. We can look at the object in the current form, and even edit it directly in the database or navigate to connected objects like the order's customer and positions/items:

As a last example, let's modify the GUI while it is running. We edit the source of the current form (in the *Top global variable), test it, and then open a "normal" editor in a split pane for a better overview and modify it again:


A first brief description of the generic PilBox Android App appeared in https://www.mail-archive.com/picolisp@software-lab.de/msg07233.html. The tarball can be downloaded from https://software-lab.de/PilBox.tgz, and a more detailed description is in https://software-lab.de/PilBox/README.

This wiki also has an article at https://picolisp.com/wiki/?PilBox.

You can download the current APK from https://software-lab.de/pilBox.apk.

I don't want to repeat the descriptions here, so I just show some practical aspects of it usage.

PilBox GUI

PilBox employs the standard PicoLisp GUI. In fact, the whole setup is identical, so that a normal PicoLisp application works right out of the box, including database and IPC. This is possible because the App starts up a normal PicoLisp Arm64 binary, and connects to it via the https://developer.android.com/reference/android/webkit/WebView.html component.

The pre-packaged PilBox comes with a slightly modified "Phone GUI" demo from https://picolisp.com/wiki/?PhoneGUI. The single monolithic file was split into smaller fragments for better modularity, the start page modified, and some Android functionality was added.

Going through some of the standard components:

Tapping on the Logo always returns to the start page. When tapped a second time, however, it opens a REPL window (also in the standard GUI, the repl function in @lib/form.l).

The repl components also allows you to edit a source file directly in the App. Useful if your phone or tablet is not rooted. As an example, we edit and modify the very page where the REPL is shown, and insert a big "TEST !!!!" in the header.

Android Toolbox

But PilBox can do much more than that. The big difference is that it has full access to the Android runtime system, allowing most things which are possible in a native App (as opposed to a plain browser-based WebApp).

The demo App allows access to the Notification manager, GPS devices, camera and QR-Code scanner. The interface functions are all in @lib/android.l, as included in the standard PicoLisp distribution.

@lib/android.l is an extension and improvement of the principles described in https://picolisp.com/wiki/?javaCode.

We can post an Android Notification directly from the REPL, with the notify function:

The notify function is defined in @lib/android.l:
   (de notify (Ttl Msg Id)
      (let B (java "android.support.v4.app.NotificationCompat$Builder" T CONTEXT)
         (java B 'setSmallIcon (java "de.software_lab.pilbox.R$drawable" "notify"))
         (java B 'setContentTitle Ttl)
         (java B 'setContentText Msg)
         (java B 'setAutoCancel T)
         (java B 'setLights `(hex "FFFFFF") 500 500)
         (java (java CONTEXT 'getSystemService "notification")  # NotificationManager
            'notify (or Id 0) (java B 'build) ) ) )
This corresponds to the standard Java SDK code:
   public void notify(String title, String msg, int id) {
      Notification.Builder b = new NotificationCompat.Builder(ctx);

      b.setLights(0xFFFFFF, 500, 500);

      NotificationManager manager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE)
      manager.notify(id, b.build());
As you see, it is a straight-forward translation.

Reading the GPS position
GPS can be read with the gps function. It uses Java objects like *LocMan (Location Manager) and *LocLsn (Location Listener):

The gps is also defined in @lib/android.l:
   (de gps ()
      (unless *LocMan
            *LocMan (java CONTEXT 'getSystemService "location")
            *LocLsn (java T "android.location.LocationListener") )
         (def 'onLocationChanged *LocLsn
               (msg Loc " onLocationChanged")
               NIL ) )
         (def 'onProviderDisabled *LocLsn
            '((Prov)) )
         (def 'onProviderEnabled *LocLsn
            '((Prov)) )
         (def 'onStatusChanged *LocLsn
            '((Prov Stat Extras)) )
         (java *LocMan 'requestLocationUpdates "gps" '(L . 20000) (-3 . 100) *LocLsn) )
      (when (java *LocMan 'isProviderEnabled "gps")
         (let? Loc (java *LocMan 'getLastKnownLocation "gps")
               (+ (java Loc 'getLatitude) 90000000)
               (+ (java Loc 'getLongitude) 180000000) ) ) ) )
The four methods of the LocationListener interface (onLocationChanged, onProviderDisabled, onProviderEnabled and onStatusChanged) are empty and currently unused. They could well be omitted. So what remains is just the creation of the LocationListener object, registering it with requestLocationUpdates, and calling getLastKnownLocation whenever needed.

The final two lines — offsetting the position with 90.0 and 180.0 — are necessary because the PicoLisp database wants to avoid negative coordinates in '+UB' trees.

Taking Pictures
Taking a picture is easy:

The GUI is just
   (bar "Camera"
      (form NIL
         (<h3> "fh" "Camera")
         (form NIL
            (gui '(+Button) "Take Picture"
               '(android~takePicture (tmp "img")
                     (setq *Picture (tmp "img"))
                     (android~loadUrl (baseHRef) *SesId "app/camera.l") ) ) )
            (gui '(+Var +Img) '*Picture NIL NIL 400 400) ) ) )
The relevant function in @lib/android.l is
   (de takePicture (Dst Fun)
      (startActivityForResult Fun
         "output" (fileUri Dst) ) )
taking a destination file name in Dst and a function accepting an Intent for handling the picture after it was taken. The workhorse for that is startActivityForResult, a very powerful mechanism for finding and running another Activity (i.e. a Camera App).

Rooted Devices
If you have rooted your device (recommended anyway for serious development work), then you don't have to bother with the REPL GUI.

Instead, you can swich to the pilBox user:

The Termux command line
   :~ su -c 'exec bash --rcfile /data/data/de.software_lab.pilbox/files/abu'
passes a small Bash source file ("abu" in my case), to set up some environment, aliases and other convenience stuff. I have also a .vimrc file in the pilBox home directory.

In this way you can do virtually everything with the code. In principle you can replace it with a different App. The PilBox itself is completely generic. All modifications will remain until you re-install the app, so you should save and restore it appropriately.


10oct22    abu
Revision History