Saturday, July 17, 2010

GUI in Bash using GTK-server

Gtk-server allows to create GUI providing access to GTK+ library functions in interpreted programming and scripting languages such as bash (it  can also be used for many libraries other than GTK).To use gtk-server in bash , it has to be started asynchronously,upon starting it reads the configuration file (more on this later) and then GTK functions can be executed by sending it as plain text to gtk-server.Each time you send some text to gtk-server it sends reply in form of strings back.These functions can be send and replies obtained by various ways.

Gtk-server comes bundled with examples for using bash via fifo(named pipes),tcp and ipc to communicate with gtk-server.However, bash 4 supports coprocesses which allows execution of a command in the background with two file descriptors connected to standard output and standard input of the command.The coprocess can be created by using coproc builtin(see man bash or type help coproc at bash prompt for more information).We will be using coprocess for communication with gtk-server.(It is possible to create something similar to coprocess using named pipes and exec in earlier bash versions,but since gtk-server allows a  -fifo=fifo-name internal argument for creation of named pipes, its not worth the effort)

First off , we need a function to communicate with gtk-server and for that we need to write a function that will do exactly that.Here is a basic function that we can use to communicate with gtk-server.

#!/bin/bash

gtk()
{ 
printf '%s\n' "$*" >&"${COPROC[1]}" 
read -ru "$COPROC" 
}

Now we can go on to start gtk-server

coproc gtk-server -stdin

This starts gtk-server asynchronously and gives us two file descriptors to communicate with gtk-server stored in COPROC array which we used in the gtk function above and also a variable COPROC_PID which contains the pid of gtk-server.

With gtk-server started and communication function defined all we have to do is send gtk functions to gtk-server.But thats not all, you would need to define these functions in gtk-server's configuration file.For now we will not define any functions and use the standard configuration file that comes with gtk-server assuming that you have the configuration file in the same directory as your script or in one of the standard locations(read the manual for more on standard location of configuration file :) ).

First we need to initialize the gtk library.In C, you would do this in main as follows:
gtk_init( &argc, &argv );
The function would initialize gtk and parse some command line arguments.Using our gtk function, we will initialize the library as
gtk gtk_init NULL NULL 

Notice that we did not pass command line arguments to gtk_init(More on this later).We would not use the REPLY read by gtk function because gtk_init does not return any value.If you do an `echo "$REPLY"` just after this line in your script, it will echo ok.Gtk-server sends back "ok" for functions which do not return anything and -1 for any function which was send to it but was not described in the configuration file.

Now, lets create our main window,we will use gtk_window_new function
gtk gtk_window_new GTK_WINDOW_TOPLEVEL
window=$REPLY 

This time we stored the reply from gtk-server into variable window.gtk_window_new returns a pointer to the newly created window.Gtk-servr in turn replies back with a Widget Id to refer to the newly created window to our client script.We store the reply from gtk-server and use the value as Widget Id every time we want to refernce to the top level window.

Next we create a button with label hello on it.
gtk gtk_button_new_with_label hello 
button=$REPLY

Its time to add the button to the container window.In gtk,you add widgets to containers.The window can be used as a container and you can add buttons to it.But generally you would add a vbox or hbox or a table to the window and pack other widgets inside it.

gtk gtk_container_add "$window" "$button" 

Now that we have our button added to the window, we are ready to show it all,we shall use gtk_widget_show_all for this but you can use gtk_widget_show for each widget too.

gtk gtk_widget_show_all "$window" 

Having everything ready, all we need to do is create a main loop:
until [[ $event = $window ]];do
  gtk gtk_server_callback wait
  event=$REPLY
done

gtk gtk_server_exit
wait "$COPROC_PID"

In the standard configuration file, gtk_window_new is defined with callback signal delete-event.So if you click on the main window close button provided by the window manager, signal
is emitted.gtk_server_callback is an internal function provided by gtk-server fetches signals .The argument wait tells it to return only when a signal has occured.By default Widget ID is returned by gtk_server_callback on a signal.So unless widget ID of the main window which we stored in variable window, is returned we continue with the loop(gtk_server_callback also updates Gtk widgets).

Once the loop ends gtk_server_exit tells gtk-server to cleanup and exit.Finally we use wait builtin to wait for gtk-server to complete.This also returns the exit status of gtk-server.

Here is all of it put together.Please note that the example has been simplified and a lot of error checking has been omitted for the purpose of illustration.However this should get you started.You would notice that the window does not look very good, you need some more GTK functions to make it look decent(Check the GTK API documnetation at gtk.org link provided at the end)

gtk()
{ 
printf '%s\n' "$*" >&"${COPROC[1]}" 
read -ru "$COPROC" 
}
 
coproc gtk-server -stdin

gtk gtk_init NULL NULL 

gtk gtk_window_new GTK_WINDOW_TOPLEVEL
window=$REPLY 

gtk gtk_button_new_with_label hello 
button=$REPLY

gtk gtk_container_add "$window" "$button" 
gtk gtk_widget_show_all "$window" 

until [[ $event = $window ]];do
  gtk gtk_server_callback wait
  event=$REPLY
done

gtk gtk_server_exit
wait "$COPROC_PID"

The configuration file not only allows you to tell gtk-server about a gtk function but also define some constants and enumerations, include another configuration file and define macros.The macros make gtk-server highly programmable(you can write a simple stand alone program in it).I will take a closer look on gtk-server configuration file and macros in my future posts.

To Be Continued...

External Links: