X11 programming in Haskell has traditionally been done using the X11 package. Many of these bindings were written by hand and, due to their reliance on FFI, are susceptible to some threading issues. xhb, a newer package, provides low-level bindings to X11 based on the same XML as xcb.
It doesn’t seem as though much has been done with XHB yet. In fact, at the time of writing (Aug 22, 2016), exactly two packages on Hackage depend on xhb. Nevertheless, I believe it hold great promise in the future of X programming in Haskell.
I’ve been working on a set of libraries that allows for layers of abstraction to be stacked on top of the low-level xhb bindings.
Here’s an overview of the libraries and their relationships:
- xhb-monad - Monad transformer for xhb server connections.
- xhb-requests - Classes and generated instances for a uniform interface to xhb.
- xhb-event-queue - Monad transformer over xhb-monad for event handling.
- xhb-mapping-state - Monad transformer over xhb-monad for keeping track of key, mod, and pointer mappings.
- wmonad - Prototype of a window manager in the XMonad tradition to demonstrate these libraries.
xhb-requests
Most generated functions in xhb have types similar to those below:
createWindow :: Connection -> CreateWindow -> IO ()
grabKeyboard :: Connection -> GrabKeyboard -> IO (Receipt GrabStatus)
Those requests which expect responses can wait for them as follows:
getReply :: Receipt a -> IO (Either SomeError a)
Some functions are a bit different (request parameters don’t live inside a dedicated type, but are passed directly to the function):
configureWindow :: Connection -> WINDOW -> ValueParam Word16 -> IO ()
queryTextExtents :: Connection -> FONTABLE -> [CHAR2B] -> IO (Receipt QueryTextExtentsReply)
Nevertheless, thanks to disciplined code generation, xhb has a clean and uniform code base. The (in progress) xhb-requests package wraps all of these generated request functions in two classes:
class Request a where
requestIO :: a -> Connection -> IO ()
class RequestWithReply a b | a -> b, b -> a where
requestWithReplyIO :: a -> Connection -> IO (IO (Either SomeError b))
The instances of this class are generated from the xhb code that is itself generated from XML, which sounds ugly, but it worked quite well. This is the foundation for xhb-monad.
xhb-monad
This package defines the following class:
class Monad x => XContext x where
request :: Request a => a -> Connection -> x ()
requestWithReply :: RequestWithReply a b => a -> Connection -> x (x (Either SomeError b))
awaitEvent :: Connection -> x SomeEvent
instance XContext IO where
= requestIO
request = requestWithReplyIO
requestWithReply = waitForEvent awaitEvent
and the following monad transformer:
class (XContext x, Monad m) => MonadX x m | m -> x where
liftX :: x a -> m a
askX :: m Connection
catchErrorX :: m a -> (SomeError -> m a) -> m a
throwErrorX :: SomeError -> m a
newtype X m a = X { runX :: ReaderT Connection (ExceptT SomeError m) a }
deriving (Functor, Applicative, Monad, MonadIO, Typeable)
instance XContext x => MonadX x (X x) where
= X . lift . lift
liftX = X ask
askX = X $ catchError (runX m) (runX . f)
catchErrorX m f = X . throwError
throwErrorX
--- mtl stuff also
instance MonadFoo Bar where...
This abstraction allows for pure X logic. It also lets us
isolate unwieldy layers of computation and bits of state in the usual
mtl
way. xhb-mapping-state is an example of this.