Exploring QML In Haskell

Posted on April 30, 2014 by Christopher Reichert

Haskell logo

Plus symbol.

Qt logo

I have recently been experimenting with the Haskell QML binding HsQML. I am a big fan of QML and it’s portability. It’s a very flexible language for user interface development and it makes for a powerful combination with Haskell.

Recently, I wrote about integrating QML code and Haskell using Fay.

HsQML, however, is a more direct way of integrating QML and Haskell. The HsQML approach has the value of the Haskell runtime and garbage collector, among other things (though, Fay may compile the garbage collector, not sure).

This post describes working with HsQML < 0.3.

As of HsQML 0.3 many of the issues I describe in this post are fixed. I will have a follow-up post soon talking about HsQML 0.3!

HsQML is a binding to the QML engine from Haskell. The library is still fairly young but it is extremely useful and can definitely get the job done. The current HsQML packages in Hackage only support Qt4/QtDeclarative but there is Qt5/QtQml support in the HsQML Darcs Repository. I have tested HsQML with Qt5 and everything seems to be working very well.

I have begun implementing a TorChat client in Haskell using HsQML. You can find the code on my Github. Contributions welcome! HSTorChat uses many basic features of HsQML and can definitely be used as an example for developing more complex user interfaces in QML.

TorChat is a simple messenger application that is built on top of Tor’s hidden services. When an HSTorChat client is talking to a buddy, outgoing messages are gauranteed to be sent to the correct onion address.

Everyone – including the introduction points, the distributed hash table directory, and of course the clients – can verify that they are talking to the right hidden service

– https://www.torproject.org/docs/hidden-services.html.en


HSTorChat


State

Managing state in a Haskell GUI application is not always trivial. I was largely unable to use Haskell data structures as QML models. I initially attempted to define read-write properties which could be updated from QML. However, I found that with lack of support for property signals I was unable to get QML to always update the views to reflect the state. I was able to work through these issues more easily in HsQML 0.3.

Because each buddy is managed on a differed thread, there must be some way to coordinate changes with the ui. The buddy list serves as the point of communication between threads and uses an MVar. When a new buddy authenticates, the corresponding onion address is added to an MVar’d buddy list using takeMVar and putMVar (or withMVar to combine the operations):

    buds' <- takeMVar $ _buddies ui'
    putMVar (_buddies ui') (b:buds')

(This should be done using withMVar)

Since each buddy is managed in it’s own thread, the MVar enables syncing messages from the ui to the corresponding buddy.

Signals

When a new message is received a signal is sent from Haskell to QML.

    m <- newObject $ Msg (T.unpack msg) onion
    fireSignal (Tagged ui :: Tagged MsgReady (ObjRef UI)) m

fireSignal is called from the thread managing the Buddy. The HsQML documentation has been updated to reflect that the function safe to call from any thread:

http://hub.darcs.net/komadori/HsQML/patch/20140507214126-4d2ae

Calling Haskell functions from QML

Calling Haskell functions from QML is quite simple. In GUI.hs, sendMsg defines a function on the main context object which takes three parameters:

 instance Object UI where
      classDef = defClass [
            , defMethod "sendMsg" sendMsg
            ...
            ]
    
    sendMsg :: ObjRef UI -> T.Text -> T.Text -> IO ()
    sendMsg ui onion msg = do
        let ui' = fromObjRef ui
        buds <- readMVar $ _buddies ui'
        sendMsgTo buds
      where
        sendMsgTo []   = putStrLn "Unable to send msg: no buddies."
        sendMsgTo buds = do
            -- Filter proper buddy from list.
            let buddy = head $ filter (λb -> _addy b == T.unpack onion) buds
            hPutStrLn (_out_conn buddy) $ lowercase $ filter (/= '"') $ show $ Message msg
            return ()

sendMsg takes a reference to the UI object, a buddy name, and a message.

I admit, It would be better to experiment with constructing and passing a complete Msg type from QML instead of the onion and msg individually. The function definition could be more readable.

If you happen to peruse the code, you might notice there is also some discrepency between the ProtocolMsg Message and Msg type. I hope to combine the two in the next few revisions of HSTorChat.

Here is an example call to sendMsg in the onAccepted slot of a QML TextInput component.

    onAccepted: { sendMsg("buddyname", "buddyonion") }

Model/View

There is no straight forward way to implement data models for QML in Haskell that I am aware of. It would be nice to support actual ListModel declarations some way from within Haskell.

Update (5/6/2014): As of HsQML 0.3 I have had success using Haskell Lists as QML data models. See here for details https://github.com/creichert/hstorchat/commit/f3175e939fa5c10735e5fcdeec02a9383fa31d74

Error reporting

One thing that is very difficult is getting the output of errors in the event of a QML crash or error creating a component. Most of the time the failure is silent. I have not yet found a way to work around this besides testing the qml file separately with qmlscene.

Update: HsQML 0.3, however, has had fantastic error reporting.

Side-Note

Attoparsec and Parsec are extremely cool parser combinator libraries for Haskell.

Anytime HSTorChat receives a new data packet on it’s input connection it attempts to apply a series of parser combinators which basically turn the message into the type of ProtocolMsg the data represents.

    data ProtocolMsg = Ping | Pong ...

    parseResponse :: Parser ProtocolMsg 
    parseResponse =  try parsePing
                 <|> try parsePong
                 <|> try parseVersion
                 <|> try parseClient
                 <|> try parseStatus
                 <|> try parseAddMe
                 <|> try parseDelayedMsg
                 <|> parseMsg

Check out the definition of the ProtocolMsg here.

Check out the definition of the combinators here.

Coming Soon

HSTorchat in it’s current state accomplishes quite a lot. My next goal is to make HSTorChat into a full fledged chat client and port it to the Raspberry Pi.

I will also be experimenting with using Fay to generate JavaScript library code in an HsQML application. More to come!

Happy Hacking!


comments powered by Disqus