talkingCode

Archive for the gtk category

Pixmap, Pixbuf and memory lane

posted by codders in code, gtk, haskell

So how’s the Haskell project going? So far, so good. When I last wrote, I’d managed to load a “Hello World!” GUI for my application. Since then I’ve managed to parse up some content and render it to the screen. Current revision is 121ed7f2.

Antediluvian file formats

The project, in case you’ve not been following on github, is to resurrect a long-since abandoned game. This game was released as a cover disk on an Amiga magazine back in “the day” and the code has since been made available by the authors (in 68000 assembly), ported to BlitzBasic and (abortively) ported to C++/SDL.

The maps for the BlitzBasic game were stored as tile maps. Each game level combines a tile set and a map to place the tiles on the screen. For additional kicks, some of the files are compressed under a scheme called “BPCK” which involves fairly simple run-length encoding. The code to parse up those files was… err… relatively straightforward to write although I did have some issues with bit-order and palette mapping.

Tiles as Pixbufs

The tiles are stored as sequences of nibbles mapping pixels on to a 16-colour palette. In order to draw these, I extracted the RGB for the nibbles and inserted that 3-byte-per-pixel sequence into a pixmap (this is the source):

buildTile :: [BPCK.PaletteEntry] -> BPCK.Gliph -> IO Pixbuf
buildTile palette g = do 
    let gData = BPCK.gliphData g
    buf <- pixbufNew ColorspaceRgb False 8 gliphX gliphY
    pbData <- (pixbufGetPixels buf :: IO (PixbufData Int Word8))
    rowStride <- pixbufGetRowstride buf
    chan <- pixbufGetNChannels buf -- Hopefully this is 3 (R,G,B)
    bits <- pixbufGetBitsPerSample buf -- Hopefully this is 8
    doFromTo 0 (gliphX - 1) $ \y ->
      doFromTo 0 (gliphY - 1) $ \x -> do
        let pixbufoffset = x*chan + y*rowStride
        let gliphOffset = fromIntegral $ x + y*gliphX
        let paletteIndex = B.index gData gliphOffset
        let thiscolor = palette !! fromIntegral paletteIndex
        writeArray pbData (pixbufoffset) (fromIntegral $ BPCK.red thiscolor)
        writeArray pbData (1 + pixbufoffset) (fromIntegral $ BPCK.green thiscolor)
        writeArray pbData (2 + pixbufoffset) (fromIntegral $ BPCK.blue thiscolor)
    return buf
    where gliphX = BPCK.gliphWidth g
             gliphY = BPCK.gliphHeight g

Maps as Pixmaps

The difference between a pixbuf and a pixmap is, as I understand it, that a pixbuf is an X-client side block of data into which you can load pixel information and a pixmap is an X-server side drawable (in the GTK sense) object. Pixbuf is more like a brush for painting, and Pixmap is an off-screen canvas. So to build my map I blatted the tile pixbufs on to a pixmap:

createTiledPixmap :: BPCK.ParsedImage -> BPCK.ParsedTileMap -> IO Pixmap
createTiledPixmap tileSet tileMap = do
    putStrLn $ "Building tile pixmaps"
    tiles <- tilesFromImageData tileSet
    let tileCount = length tiles
    putStrLn $ "Creating new pixmap " ++ show totalWidthPixels ++ " x " ++ show totalHeightPixels
    pixmap <- pixmapNew (Nothing :: Maybe DrawWindow) totalWidthPixels totalHeightPixels (Just 24)
    gc <- gcNew pixmap
    doFromTo 0 (tilesHigh - 1) $ \iy ->
      doFromTo 0 (tilesAcross - 1) $ \ix -> do
        let tileIndex = ix + (iy * tilesAcross)
        let tileId = (min (fromIntegral (BPCK.tileMap tileMap !! tileIndex)) tileCount) `mod` tileCount
        let curX = ix * tileSizePixels
        let curY = iy * tileSizePixels
        postGUIAsync $ drawPixbuf pixmap gc (tiles !! tileId) 0 0 curX curY tileSizePixels tileSizePixels RgbDitherNone 0 0
    return pixmap
    where tileSizePixels = BPCK.gliphSize tileSet
             tilesAcross = BPCK.tilesAcross tileMap
             tilesHigh = BPCK.tilesHigh tileMap
             totalWidthPixels = tileSizePixels * tilesAcross
             totalHeightPixels = tileSizePixels * tilesHigh

Results

The finished article looks a bit like this:

Screenshot
or this:

Screenshot
and pressing the space bar cycles through the maps on account of this:

onKeyPress app (\x@(Key { eventKeyName = name,
                              eventKeyChar = char }) -> do
    case char of
      Just ' ' -> do
        putStrLn $ "Switching map"
        currentState <- readIORef mapStateRef
        nextState <- nextMapState currentState
        writeIORef mapStateRef nextState
        drawWin <- widgetGetDrawWindow canvas
        gc <- gcNew drawWin
        (width, height) <- drawableGetSize drawWin
        postGUIAsync $ drawDrawable drawWin gc (renderedMap nextState) 0 0 0 0 width height
      Just c -> putStrLn $ "Press " ++ name ++ "('" ++ [c] ++ "')"
      Nothing -> putStrLn $ "weird key: " ++ name
    return (eventSent x))

Conclusions

I’m now a lot more comfortable with Haskell and think I’ve mastered the “word-for-word translation of imperative to pseudo-functional” style. The file parsing code I’ve written performs like a dog on account of that, but it’s not on a critical path for anything. One thing that has struck me is how easy it is to refactor Haskell code (as long as you use hanging ‘do’s to avoid formatting upsets). So I’ll stick at it and see if I can’t finish this project inside of a year.

Why not SDL?

FYYFDANSEIC, etc.

GTK, Glade, Haskell and gnome_program_init()

posted by codders in c, code, gtk, haskell

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
#include 

void 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 :)

Haskell, GTK and Multi-Threading

I have been working on an application in Haskell, using Gtk2Hs for the user interface. Now, you normally want a graphical user interface (GUI) to be responsive, so you avoid doing tasks that take a long time in the thread that handles the GUI. Instead, your main computation happens in other application threads, and all that happens in the GUI thread is updating of interface elements. It turns out that there are several issues that come up when you mix Haskell, GTK and multiple threads, which is why this post is here.

Part 1: The GTK event loop

Most programs that use Gtk2Hs first do all the GUI-related initialization, and then execute the mainGUI computation. This is actually a loop that processes GTK-related events until the user quits the program. Because of the way thread switching works in Haskell, a thread executing a loop like that will not let any other “lightweight” threads run. (Lightweight threads are threads created using the forkIO computation.) One frequently suggested way to solve this problem is to make the GTK event loop periodically yield to any other Haskell threads by adding the following to your GUI initialization code:

timeoutAddFull (yield >> return True) priorityDefaultIdle 100

Note that this is only an issue when all lightweight Haskell threads run on top of a single operating system thread, as in the single-threaded RTS of GHC. If Haskell threads are allowed to run on multiple OS threads, then yielding is not necessary.

Part 2: GTK thread safety

It turns out that that GTK (the C library wrapped by Gtk2Hs) is not thread-safe. What this means is that all modifications of GTK state must happen from a single OS thread, which also must be the same thread that is executing the GTK event loop. If all Haskell threads are run on top of a single OS thread, this is easy to ensure. However, if you use a system where lightweight threads may be mapped to different OS threads (such as in the multi-threaded RTS in GHC), care must be taken when accessing a GUI. Essentially, application threads need to put GUI modification events onto a queue, with the thread that runs the GTK event loop processing the events from that queue. Fortunately, Gtk2Hs comes with such a queue built-in; you can use the postGUI functions to give the event loop blocks of IO to execute.

Note that, by default, Gtk2Hs will produce a warning when running under the GHC multi-threaded RTS. To get rid of this, useĀ unsafeInitGUIForThreadedRTS instead of the usual initGUI to perform GTK initialization. The “unsafe” part of the computation name signifies that you are aware of the requirement to only modify GTK state from the correct OS thread.

Part 3: Deconstructing the GTK event loop

There might come a time when you want to do something more complicated than the above solutions allow. In such a case, you can actually substitute the mainGUI event loop with your own. Gtk2Hs provides functions that will process a single event at a time off the GTK event queue. Put these in a loop, sprinkle with your custom logic (such as pulling events off multiple queues), and you’re done!

Context

This was written with the GHC environment in mind. Other Haskell compilers and/or interpreters may differ in their implementations.

Recent Posts
Recent Comments
About Us
jp: works like a charm! thanks!...
Blake: Check this out: http://bugs.adobe.com/jira/browse/SDK-28016...
Boydell: Wow. That was it. You are the only one that had it figured out, and I looked at many...
mark van schaik: thanks! was using a beta SDK version for a production app, which stopped working over...
Sebastian: Steve, I find most asynchronous programming to be incredibly painful. Haskell's appro...

This is the personal blog of a professional software engineer. This site and the views expressed on it are in no way endorsed by the RIAA.