GTK, Glade, Haskell and gnome_program_init()
So. I finished the book (at length, and to be fair I mostly skimmed the last chapters), which means it’s time for me to start actually writing Haskell code. I thought I’d start with a simple GUI app, but it turned out not to be quite so simple.
I’ve put the code that I’m working on up on GitHub because that’s what all the cool kids are doing. My project is called GP3 for reasons that ought eventually to become clear. HEAD at time of writing is 7b01940
Glade
Glade is a GTK UI designer. I won’t go in to a lot of detail – there’s been plenty written about it. What I will say, though, is that at version 3, you can often find yourself creating unexpected dependencies for your program by using the more complex widgets. I unwittingly picked something from the “GNOME User Interface” toolbox, which has a Glade class of “GnomeApp”. This includes a “BonoboDock” and a “BonoboDockItem”.
Launching your app
Borrowing heavily from the book, here’s part of the code I was using to launch my app:
main :: FilePath -> IO ()
main gladepath =
do
unsafeInitGUIForThreadedRTS
timeoutAddFull (yield >> return True) priorityDefaultIdle 100
gui <- loadGlade gladepath
connectGui gui
windowPresent (mainApp gui)
mainGUI
gnome_program_init()
Having made the mistake of using a GNOME-UI widget, I saw this when I ran my app:
GnomeUI-ERROR **: You must call gnome_program_init()
before creating a GnomeApp
This is because my Glade UI requires libgnomeui to be initialised. To make matters worse, libgnomeui isn't linked by default in to Gtk2Hs. In my limited understanding of Haskell and Gnome, there are two options at this point. One is to import the gnome_program_init function from libgnomeui over FFI. The other is to write a C program to wrap a call to gnome_program_init and re-export a simpler function for you to import over FFI. I chose the latter option:
// gtk_docker.c #includevoid do_gnome_init() { static char **argv = NULL; if (argv == NULL) { argv = malloc(2); argv[0] = "gtk_docker"; argv[1] = '\0'; } gnome_init("my-app", "my-version", 1, argv); }
... and a header file
// gtk_docker.h void do_gnome_init(void);
Astute observers will see that this is a bit of a cheat. GnomeUI wants the command line arguments that were passed to the executable. It would be possible, but irritating, to arrange this. I couldn't easily divine how to pass an array of CStrings over FFI, so I wimped out. Also, I'm not technically calling gnome_program_init - this call appears to be deprecated in favour of gnome_init, and the latter call also silences the error message.
Compilation
We haven't solved the compilation problem yet. The compiler still needs to know where to find gnome.h and its included headers, and needs to know where to find the associated libraries for linking. There are good ways and bad ways to solve this problem... here's a bad way:
#Makefile
LDFLAGS = -lgnomeui-2 -lcairo -lglade-2.0
CFLAGS = -I/usr/include/libgnomeui-2.0 -I/usr/include/gtk-2.0/ \
-I/usr/include/cairo/ -I/usr/include/glib-2.0/ \
-I/usr/lib/glib-2.0/include/ \
-I/usr/include/pango-1.0/ \
-I/usr/lib/gtk-2.0/include/ \
-I/usr/include/atk-1.0/ \
-I/usr/include/libgnome-2.0/ \
-I/usr/include/libbonobo-2.0/ \
-I/usr/include/libgnomecanvas-2.0/ \
-I/usr/include/libart-2.0/ \
-I/usr/include/libbonoboui-2.0/ \
-I/usr/include/gnome-vfs-2.0/ -Werror -Wall
GHCC=ghc
default: gp3
gp3: gtk_docker.o GP3Main.hs GP3GUI.hs
$(GHCC) --make $(LDFLAGS) $^
clean:
rm -f *.o *.hi GP3Main
The real answer probably involves GNU AutoTools for the C toolchain or some craziness with Cabal. I'm sure I'll get round to that
Calling the function
Now to clear that error message. We just need to...
{-# LANGUAGE ForeignFunctionInterface #-}
foreign import ccall unsafe "gtk_docker.h do_gnome_init"
c_gnome_init :: IO ()
and
main :: FilePath -> IO ()
main gladepath =
do
unsafeInitGUIForThreadedRTS
c_gnome_init
...
and we're done.
Portability
Linking libgnomeui probably makes my code a lot less portable. Hard-coding the include paths certainly does. Fortunately I don't have to care about other users just yet, and I'm unlikely ever to care about other platforms