GTK-Stream : A stream-based GUI protocol
Ever felt tired of all the fake "asynchronous" nonsense you have to muddle through when creating GUIs ?
All those callbacks and the frameworks that take over your entire program ?
Ever wanted to just say "open a window, containing a button X and a label. When X is clicked, open another window. Oh, and make the label red" ?
Ever wanted your GUIs to read like regular old single-threaded code ?
Well now they can !
GTK-Stream is a plain, old, Unix command that reads a GUI description from its standard input (formatted in XML, the bestest of languages) and outputs GUI events on its standard output.
Here is an example of what that looks like, with a GUI managed entirely in the simplest of Bash :
#!/usr/bin/env bash
# You first start gtk-stream as a coprocess
coproc GUI { gtk-stream; }
# kill the coprocess when the script exits
trap "kill $GUI_PID" EXIT
exec {events}<&"${GUI[0]}" # events are read from the coprocess' stdout
exec {gui}>&"${GUI[1]}" # gui modifications are written to its stdin
function send_gui() {
printf "$@" >&"$gui"
}
send_gui '<application>' # You need to start an application
send_gui '<window id="window1">' # then you can open a window
# ... containing a button
send_gui '<button id="hello"><label text="Hello" /></button>'
send_gui '</window>' # ... and nothing else
# At this point, the window pops up, and you can click the button up to three times
i=0
while read -u "$events" ev; do
printf "Event: %s\n" ev
case "$ev" in
*:clicked)
((i++))
if ((i > 3)); then break; fi
;;
esac
done
# Once this is done, close the window
send_gui '<close-window id="window1" />'
# ... and the app
send_gui '</application>'
Installing GTK Stream
Gtk-Stream is on PyPI !
So to install it, you can just run pip3 install gtk-stream
and, if
all goes well, start using it right away.
Otherwise, if you know and love Nix, there is
also a default.nix
file in the repository that should provide you
with all its streaming goodness. You may build it with nix-build
default.nix
, or install it with nix-env -f default.nix -i
.
Using GTK Stream
Please see the GTK-Stream protocol for a full description of how to interact with GTK-Stream.
But why ?
Writing GUIs is a complex endeavour. It doesn't have to be this way.
Callbacks are hard
Most, if not all, GUI toolkits provide a way to handle user input through asynchronous callbacks. When a button is clicked, a function is called. When a user types something in a text field, a function is called. When the application is ready to run, a function is called.
There are several problems with this approach, especially for simple applications :
-
callbacks are contagious. Kind of like asynchronous functions in Javascript before async/await (and even since then, async functions may never again become synchronous).
If you have a function that, instead of producing a result, takes a callback as argument, all functions that would use that result must be written in a continuation-like manner.
-
callbacks force a sometimes unnatural splitting of local state, and coupling of business logic and interaction semantics.
Take the above example of a button pressed three times. The state of the application is stored in the plain ol' variable
i
, and mutated when the button is pressed. A simple, linear control flow.The same logic, if using callbacks, would be achieved by storing that state variable
i
some place where it could be accessed by the button being clicked (by subscribing to a 'click' event). In that context, the callback must also be able to access the window, in order to close it.In short, that callback would need to own a reference to both a graphical component (the window) and a logical one (the variable).
-
callbacks are complicated to think about. Much more so than a basic "when this happens, do that" model.
Language-independence
Since GUI toolkits depend on callbacks, it means that GUI can only be written in languages whose function call semantics can be understood by the toolkit. This means C, or through some sort of FFI if your language supports it.
This may prove impossible, for instance, for shell languages, where functions don't adhere to any C calling convention whatsoever.
In contrast, all languages provide a way to read and write plain strings to a pipe, and read plain strings back from another pipe.
Instrumentation
One of the interesting aspects of the Unix shell is how it allows you to easily compose different unrelated commands to arrive at a result.
Not so with GUI applications, though. GUIs are usually monolithic, and need user interaction to provide certain results.
Using Gtk-Stram may enable a new kind of application : the GUI-able kind. Indeed, a script that has been written to follow the Gtk-Stream protocol may well be connected to another program, that may not spawn windows and instead responds with pre-recorded events.
Conclusion
For all those reasons, when writing a simple user interface (one asking to open a file, or showing a few progress bars inching along), it's not worth the effort to write a GUI.
Maybe that is why most of graphical applications that exist are for complex programs. The added complexity of a GUI toolkit doesn't matter as much when your program is already doing a lot of complex things.
But it sure matters when writing basic scripts, that expect simple interactions. Those scripts seem relegated to the command-line, where interaction is linear and synchronous. And it is not pretty, nor nearly as friendly.
No more ! Now, simple scripts can also boast nice-looking GUIs,
simply by being piped to and from a running gtk-stream
process.