Personal tools

X window programming in Haskell

From HaskellWiki

(Difference between revisions)
Jump to: navigation, search
(last sections, removed my signature and any reference to xmobar. I'm not going to edit this stuff anymore.)
(desultory formatting and copyeditting)
Line 1: Line 1:
==Writing an X application with Haskell Xlib bindings==
 
 
 
This tutorial will show you how to write a simple X application using
 
This tutorial will show you how to write a simple X application using
the low level Xlib library. The goal is to write a simple text base
+
the low level C library, Xlib<!-- For future reference, we'll probably need to expand this page when the XCB Haskell binding is done. -->. The goal is to write a simple text-based clock displaying the system time, running on top of every other application - like a status bar would.
clock, that will display the system time, to be run on top of every
 
other applications, like a status bar.
 
   
While the application is fairly simple, still it will require us to
+
While the application is fairly simple, it still requires us to know quite a few details about X and Xlib to write a proper X application.
get to know quite a lot of the details that must be taken into account
 
when writing a properly working X application.
 
 
Obviously some understanding of X and Xlib is required.
 
 
These are some links that can be used as reference:
 
 
* [http://www.tronche.com/gui/x/xlib/ The Xlib Manual]: this is the reference manual, and you should look up here every function that we are going to use in this tutorial.
 
* [http://en.wikipedia.org/wiki/Xlib Xlib (Wikipedia)]
 
* [http://en.wikipedia.org/wiki/X_Window_System_core_protocol X Window System core protocol (Wikipedia)]
 
   
  +
==Target audience==
 
This tutorial is dedicated to the intermediate Haskell coder. While I
 
This tutorial is dedicated to the intermediate Haskell coder. While I
 
will try to write the simplest code I can (probably it will even look
 
will try to write the simplest code I can (probably it will even look
Line 11: Line 10:
 
Haskell part.
 
Haskell part.
   
  +
==Goals==
 
What are we going to learn:
 
What are we going to learn:
 
* how to create a window and set, or change, its attributes;
 
* how to create a window and set, or change, its attributes;
* how to draw in that window, specifically some text, with some properties, like fonts or colors;
+
* how to draw in that window, specifically some text, with some properties, like
  +
fonts or colors;
 
* how to properly update the window;
 
* how to properly update the window;
 
* how to handle events, like a mouse button press.
 
* how to handle events, like a mouse button press.
   
  +
==Background reading==
  +
These are some links that can be used as reference:
  +
* [http://www.tronche.com/gui/x/xlib/ The Xlib Manual]: this is the reference
  +
manual, and you should look up here every function that we are going to use in
  +
this tutorial.
  +
* [http://en.wikipedia.org/wiki/Xlib Xlib (Wikipedia)]
  +
* [http://en.wikipedia.org/wiki/X_Window_System_core_protocol X Window System core
  +
protocol (Wikipedia)]
  +
  +
==Prerequisites==
 
In order to compile the following code examples you need at least:
 
In order to compile the following code examples you need at least:
  +
* [http://hackage.haskell.org/cgi-bin/hackage-scripts/package/X11 X11], the
  +
Haskell binding to the X11 graphics library.
  +
* [http://hackage.haskell.org/cgi-bin/hackage-scripts/package/X11-extras
  +
X11-extras]: will be required in some examples. This library provides missing
  +
bindings to the X11 graphics library and is being actively developed by Spencer Janssen at the time of this writing; it is notably being used in [[Xmonad]]. Some functions needed in this tutorial can be found only in the darcs repository of X11-extras: [http://darcs.haskell.org/~sjanssen/X11-extras
  +
http://darcs.haskell.org/~sjanssen/X11-extras]. Read carefully the README before
  +
installing.
   
* [http://hackage.haskell.org/cgi-bin/hackage-scripts/package/X11 X11], the Haskell binding to the X11 graphics library.
+
==Hello world!==
* [http://hackage.haskell.org/cgi-bin/hackage-scripts/package/X11-extras X11-extras]: will be required in some examples. This library provides missing bindings to the X11 graphics library and is actively developed by Spencer Janssen at the time of this writing. Some functions needed in this tutorial can be found only in the darcs repository of X11-extras: [http://darcs.haskell.org/~sjanssen/X11-extras http://darcs.haskell.org/~sjanssen/X11-extras]. Read carefully the README before installing.
+
Let's start with the usual simple "Hello World":
 
 
 
==Hello world==
 
 
Let's start from the usual simple "Hello World"
 
   
 
<haskell>
 
<haskell>
 
 
module Main where
 
module Main where
 
import Graphics.X11.Xlib
 
import Graphics.X11.Xlib
Line 31: Line 47:
 
main :: IO ()
 
main :: IO ()
 
main =
 
main =
do dpy <- openDisplay ""
+
do dpy <- openDisplay ""
let dflt = defaultScreen dpy
+
let dflt = defaultScreen dpy
border = blackPixel dpy dflt
+
border = blackPixel dpy dflt
background = whitePixel dpy dflt
+
background = whitePixel dpy dflt
rootw <- rootWindow dpy dflt
+
rootw <- rootWindow dpy dflt
win <- createSimpleWindow dpy rootw 0 0 100 100 1 border background
+
win <- createSimpleWindow dpy rootw 0 0 100 100 1 border background
setTextProperty dpy win "Hello World" wM_NAME
+
setTextProperty dpy win "Hello World" wM_NAME
mapWindow dpy win
+
mapWindow dpy win
sync dpy False
+
sync dpy False
threadDelay (10 * 1000000)
+
threadDelay (10 * 1000000)
exitWith ExitSuccess
+
exitWith ExitSuccess
 
 
</haskell>
 
</haskell>
   
The first function,
+
The first function,
[http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Display.html#v%3AopenDisplay openDisplay],
+
[http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Display.html#v%3AopenDisplay
is the interface to the Xlib function
+
openDisplay], is the interface to the Xlib function [http://www.tronche.com/gui/x/xlib/display/opening.html XOpenDisplay()], and opens a connection to the X sever that controls a display. The connection is returned and bound to dpy. By applying [http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Display.html#v%3AdefaultScreen
[http://www.tronche.com/gui/x/xlib/display/opening.html XOpenDisplay()],
+
defaultScreen], the interface to [http://www.tronche.com/gui/x/xlib/display/display-macros.html#DefaultScreen
and opens a connection to the X sever that controls a display. The connection is returned and bound to dpy. By applying
+
XDefaultScreen], we get the default screen, that is required in many of the following functions. With [http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Display.html#v%3ArootWindow
[http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Display.html#v%3AdefaultScreen defaultScreen],
+
rootWindow], the interface to [http://www.tronche.com/gui/x/xlib/display/display-macros.html#RootWindow
the interface to
+
XRootWindow()], we get the root window. We need it in order to set the parent window in the most important function of the above code: [http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Window.html#v%3AcreateSimpleWindow
[http://www.tronche.com/gui/x/xlib/display/display-macros.html#DefaultScreen XDefaultScreen],
+
createSimpleWindow], the interface to [http://www.tronche.com/gui/x/xlib/window/XCreateWindow.html XCreateSimpleWindow].
we get the default screen, that is required in many of the following functions.
 
With
 
[http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Display.html#v%3ArootWindow rootWindow],
 
the interface to
 
[http://www.tronche.com/gui/x/xlib/display/display-macros.html#RootWindow XRootWindow()],
 
we get the root window. We need it in order to set the parent window in the most important function of the above code:
 
[http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Window.html#v%3AcreateSimpleWindow createSimpleWindow],
 
the interface to
 
[http://www.tronche.com/gui/x/xlib/window/XCreateWindow.html XCreateSimpleWindow].
 
   
This function takes, as arguments: the display, the parent window of
+
This function takes as arguments: the display, the parent window of
the window to be created, the x position, the y position, the width,
+
the window to be created, the ''x'' position, the ''y'' position, the width,
the height, the border width, the border pixel and the background
+
the height, the border width, the border pixel, and the background
 
pixel.
 
pixel.
   
The x and y positions are relative to the upper left corner of the
+
The ''x'' and ''y'' positions are relative to the upper left corner of the
 
parent window's inside borders.
 
parent window's inside borders.
   
 
In order to retrieve the values of the black and white pixels for that
 
In order to retrieve the values of the black and white pixels for that
 
specific screen, we use two specific functions:
 
specific screen, we use two specific functions:
[http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Display.html#v%3AblackPixel blackPixel],
+
[http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Display.html#v%3AblackPixel
the interface to the X11 library function
+
blackPixel], the interface to the X11 library function
[http://www.tronche.com/gui/x/xlib/display/display-macros.html#BlackPixel BlackPixel],
+
[http://www.tronche.com/gui/x/xlib/display/display-macros.html#BlackPixel
and
+
BlackPixel], and [http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Display.html#v%3AwhitePixel
[http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Display.html#v%3AwhitePixel whitePixel],
+
whitePixel], the interface to the X11 library function
the interface to the X11 library function
+
[http://www.tronche.com/gui/x/xlib/display/display-macros.html#WhitePixel
[http://www.tronche.com/gui/x/xlib/display/display-macros.html#WhitePixel WhitePixel]
+
WhitePixel]
   
The function createSimpleWindow will return the window ID and, with
+
The function createSimpleWindow will return the window ID and, with this ID, we can start manipulating our newly created window, as we do, in the above code, with the function [http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Misc.html#v%3AsetTextProperty
this ID, we can start manipulating our newly created window, as we do,
+
setTextProperty], interface to the X11 library function [http://www.tronche.com/gui/x/xlib/ICC/client-to-window-manager/XSetTextProperty.html
in the above code, with the function
+
XSetTextProperty()].
[http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Misc.html#v%3AsetTextProperty setTextProperty],
 
interface to the X11 library function
 
[http://www.tronche.com/gui/x/xlib/ICC/client-to-window-manager/XSetTextProperty.html XSetTextProperty()].
 
   
This function is needed, in our code, to set the window's name, that
+
This function is needed by our code to set the window's name so your window manager will display on some decoration attached to the window (other window managers will not display anything, for instance a tiling WM like Xmonad)
your window manager will display on some decoration attached to the
 
window (other window managers will not display anything, for instance
 
a tiling WM like [[Xmonad]])
 
   
To set the window's name we need to manipulate the
+
To set the window's name we need to manipulate the [http://www.tronche.com/gui/x/xlib/ICC/client-to-window-manager/converting-string-lists.html XTextProperty structure].
[http://www.tronche.com/gui/x/xlib/ICC/client-to-window-manager/converting-string-lists.html XTextProperty structure].
 
   
Properties, such as the XTextProperty, have a string name and a
+
Properties, such as the XTextProperty, have a string name and a numerical identifier called an atom. An atom is an ID that uniquely identifies a particular property. Property name strings are typically all upper case - with the first letter in low case when translated into Haskell - with words separated by underscores. In our example, we set the WM_NAME property to "Hello World".
numerical identifier called an atom. An atom is an ID that uniquely
 
identifies a particular property. Property name strings are typically
 
all upper case - with the first letter in low case when translated
 
into Haskell - with words separated by underscores. In our example we
 
had to set the WM_NAME property to "Hello World".
 
   
Creating and manipulating a window is just the first step to have a
+
Creating and manipulating a window is just the first step to have a new window displayed. In order for the window to become visible we must map it with
new window displayed. In order for the window to become visible we
+
[http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Window.html#v%3AmapWindow
must map it with
+
mapWindow], the interface to the X11 library function [http://www.tronche.com/gui/x/xlib/window/XMapWindow.html XMapWindow()]. This will make the window visible.
[http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Window.html#v%3AmapWindow mapWindow],
 
the interface to the X11 library function
 
[http://www.tronche.com/gui/x/xlib/window/XMapWindow.html XMapWindow()].
 
This will make the window visible.
 
   
Xlib will not send requests and calls to the Xserver immediately, but
+
Xlib will not send requests and calls to the X server immediately, but will buffer them and send the full buffer when some given conditions are met.
will buffer them and send the full buffer when some given conditions
 
are met.
 
   
One way to force the flushing of the output buffer is to call
+
One way to force the flushing of the output buffer is to call [http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Event.html#v%3Async
[http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Event.html#v%3Async sync],
+
sync], the interface to the X11 library function [http://www.tronche.com/gui/x/xlib/event-handling/XSync.html XSync()], which takes 2 arguments: the connection (dpy) and a Boolean value that indicates whether XSync() must discard all events on the event queue.
the interface to the X11 library function
 
[http://www.tronche.com/gui/x/xlib/event-handling/XSync.html XSync()],
 
which takes 2 arguments: the connection (dpy) and a Boolean value that
 
indicates whether XSync() must discard all events on the event queue.
 
   
After that the Xserver will eventually display our window.
+
After that, the X server will eventually display our window.
   
The rest of the above example does nothing else but blocking the
+
The rest of the above example does nothing else but block execution for 10 seconds (to let you stare at your newly created window) and then exits.
program execution for 10 seconds (to let you stare at your newly
 
created window) and then will exit.
 
   
==Window's attributes==
+
==Window attributes==
  +
Even though in our "Hello World" example we set the window's dimension, we have no assurance that the window manager will respect our decision.
   
Even though in our "Hello World" example we set the window's
+
Xmonad, for instance, will just create a window with the dimensions needed to fill its tiled screen, no matter what you set in createSimpleWindow.
dimension, we have no assurance that the Window Manager will respect
 
our decision.
 
   
[[Xmonad]], for instance, will just create a window with the
+
But we decided to write a small clock that will behave as a statusbar, that is to say, we want to create a window that will specifically ''not'' be managed by the window manager.
dimensions needed to fill its tiled screen, no matter what you set in
 
createSimpleWindow.
 
 
But we decided to write a small clock that will behave as a status
 
bar, that is to say, we want to create a window that will not be
 
managed by a Window Manager.
 
   
 
In order to achieve this result we need to start dealing with window's
 
In order to achieve this result we need to start dealing with window's
 
attributes.
 
attributes.
   
There are two ways of dealing with window's attributes: the first is
+
There are two ways of dealing with window's attributes: the first is to set them at window's creation time, but in that case createSimpleWindow is not powerful enough.
to set them at window's creation time, but in that case
 
createSimpleWindow is not powerful enough.
 
   
The second way is to change window's attributes after the window's has
+
The second way is to change window's attributes after the window's has been created. This second approach is not implemented
been created. This second approach is not implemented
+
[http://hackage.haskell.org/cgi-bin/hackage-scripts/package/X11 X11] but has been implemented in the darcs version of
[http://hackage.haskell.org/cgi-bin/hackage-scripts/package/X11 X11] but
+
[http://hackage.haskell.org/cgi-bin/hackage-scripts/package/X11-extras
has been implemented in the darcs version of
+
X11-extras].
[http://hackage.haskell.org/cgi-bin/hackage-scripts/package/X11-extras X11-extras].
 
   
 
===Setting window's attribute at creation time===
 
===Setting window's attribute at creation time===
+
In order to set window's attributes at creation time, the window must be created
In order to set window's attributes at creation time, the window must be created with
+
with
[http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Window.html#v%3AcreateWindow createWindow],
+
[http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Window.html#v%3AcreateWindow
the interface to the X11 library function
+
createWindow],
  +
the interface to the X11 library function
 
[http://www.tronche.com/gui/x/xlib/window/XCreateWindow.html XCreateWindow()].
 
[http://www.tronche.com/gui/x/xlib/window/XCreateWindow.html XCreateWindow()].
   
 
The type signature of this function is quite long:
 
The type signature of this function is quite long:
 
 
<haskell>
 
<haskell>
+
createWindow :: Display -> Window
createWindow :: Display -> Window
+
-> Position -> Position
-> Position -> Position
+
-> Dimension -> Dimension
-> Dimension -> Dimension
+
-> CInt
-> CInt
+
-> CInt
-> CInt
+
-> WindowClass
-> WindowClass
+
-> Visual
-> Visual
+
-> AttributeMask
-> AttributeMask
+
-> Ptr SetWindowAttributes
-> Ptr SetWindowAttributes
+
-> IO Window
-> IO Window
 
 
 
</haskell>
 
</haskell>
   
 
That is to say:
 
That is to say:
 
* the connection and the parent window
 
* the connection and the parent window
* the x and y position (origins in the upper left corner of the inside border of the parent window)
+
* the ''x'' and ''y'' position (origins in the upper left corner of the inside border of
  +
the parent window)
 
* width and height
 
* width and height
 
* border width
 
* border width
Line 141: Line 156:
 
* the visual
 
* the visual
 
* the attribute mask
 
* the attribute mask
* and the pointer to the [http://www.tronche.com/gui/x/xlib/window/attributes/#XSetWindowAttributes XSetWindowAttributes] foreign C structure.
+
* and the pointer to the
  +
[http://www.tronche.com/gui/x/xlib/window/attributes/#XSetWindowAttributes
  +
XSetWindowAttributes] foreign C structure.
   
This last one gives you an idea of the type of operation we must do in
+
This last one gives you an idea of the type of operation we must do in order to create a window (createSimpleWindow is just a specialization of this more complicated createWindow, with some arguments filled in with defaults): we need a function to allocate some memory for the creation of the foreign C structure, and then manipulate this foreign structure from within this function.
order to create a window (createSimpleWindow is just a wrapper around
 
this more complicated createWindow, with some default arguments): we
 
need a function to allocate some memory for the creation of the
 
foreign C structure, and then manipulate this foreign structure from
 
within this function.
 
   
The needed function is
+
The needed function is [http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Misc.html#v%3AallocaSetWindowAttributes allocaSetWindowAttributes], whose type indeed is:
[http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Misc.html#v%3AallocaSetWindowAttributes allocaSetWindowAttributes],
 
whose type indeed is:
 
   
 
<haskell>
 
<haskell>
 
 
allocaSetWindowAttributes :: (Ptr SetWindowAttributes -> IO a) -> IO a
 
allocaSetWindowAttributes :: (Ptr SetWindowAttributes -> IO a) -> IO a
 
 
</haskell>
 
</haskell>
   
allocaSetWindowAttributes will take a function which takes the pointer
+
allocaSetWindowAttributes will take a function which takes the pointer to the foreign structure as its argument. This function will perform an IO action that is the action returned by allocaSetWindowAttributes.
to the foreign structure as its argument. This function will perform
 
an IO action that is the action returned by allocaSetWindowAttributes.
 
   
In our case allocaSetWindowAttributes will take a function that will
+
In our case allocaSetWindowAttributes will take a function that will use createWindow to return the new window.
use createWindow to return the new window.
 
   
So, we will need to use createWindow inside allocaSetWindowAttributes.
+
So, we will need to use createWindow inside allocaSetWindowAttributes. We will soon see how. But first let's analyze the other arguments of createWindow.
We will soon see how. But first let's analyze the other arguments of
 
createWindow.
 
   
The display, the parent window, the coordinates and dimensions are the
+
The display, the parent window, the coordinates and dimensions are the same as with createSimpleWindow. But now we must specify the depth of the screen, the window's class, the visual and the attribute mask. We also need to manipulate the XSetWindowAttribute after its creation by allocaSetWindowAttributes, before calling createWindow.
same as with createSimpleWindow. But now we must specify the depth of
 
the screen, the window's class, the visual and the attribute mask. We
 
also need to manipulate the XSetWindowAttribute after its creation by
 
allocaSetWindowAttributes, before calling createWindow.
 
   
The depth is the number of bits available for each pixel to represent
+
The depth is the number of bits available for each pixel to represent colors while the visual is way pixel values are translated to produce colors on the monitor.
colors while the visual is way pixel values are translated to produce
 
colors on the monitor.
 
   
We are going to use
+
We are going to use [http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Screen.html#v%3AdefaultDepthOfScreen defaultDepthOfScreen], interface to the X11 library function [http://www.tronche.com/gui/x/xlib/display/image-format-macros.html#DefaultDepthOfScreen XDefaultDepthOfScreen()], in order to retrieve the default screen depth.
[http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Screen.html#v%3AdefaultDepthOfScreen defaultDepthOfScreen],
 
interface to the X11 library function
 
[http://www.tronche.com/gui/x/xlib/display/image-format-macros.html#DefaultDepthOfScreen XDefaultDepthOfScreen()],
 
in order to retrieve the default screen depth.
 
   
For the visual we are going to use
+
For the visual we are going to use [http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Screen.html#v%3AdefaultVisualOfScreen defaultVisualOfScreen], interface to the X11 library function [http://www.tronche.com/gui/x/xlib/display/image-format-macros.html#DefaultVisualOfScreen DefaultVisualOfScreen].
[http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Screen.html#v%3AdefaultVisualOfScreen defaultVisualOfScreen],
 
interface to the X11 library function
 
[http://www.tronche.com/gui/x/xlib/display/image-format-macros.html#DefaultVisualOfScreen DefaultVisualOfScreen].
 
   
The
+
The
[http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Types.html#t%3AWindowClass WindowClass]
+
[http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Types.html#t%3AWindowClass
can either be copyFromParent, inputOutput, or inputOnly. In the first case the
+
WindowClass] can either be copyFromParent, inputOutput, or inputOnly. In the first case the class is copied from the class of the parent window. An inputOnly window can only be used for receiving input events. In our code we are going to use inputOutput windows, windows that can receive input events and that can also be used to display some output.
class is copied from the class of the parent window. An inputOnly
 
window can only be used for receiving input events. In our code we are
 
going to use inputOutput windows, windows that can receive input events
 
and that can also be used to display some output.
 
   
The attributeMask «specifies which window attributes are defined in
+
The attributeMask "specifies which window attributes are defined in the attributes argument. This mask is the bitwise inclusive OR of the valid attribute mask bits. If value mask is zero, the attributes are ignored and are not referenced." (see [http://www.tronche.com/gui/x/xlib/window/XCreateWindow.html http://www.tronche.com/gui/x/xlib/window/XCreateWindow.html]).
the attributes argument. This mask is the bitwise inclusive OR of the
 
valid attribute mask bits. If value mask is zero, the attributes are
 
ignored and are not referenced.»
 
(see [http://www.tronche.com/gui/x/xlib/window/XCreateWindow.html http://www.tronche.com/gui/x/xlib/window/XCreateWindow.html]).
 
   
 
In other words, in order to set more then one attribute, you need to pass a value mask such as:
 
In other words, in order to set more then one attribute, you need to pass a value mask such as:
   
 
<haskell>
 
<haskell>
 
 
attrmask = cWOverrideRedirect .|. cWBorderPixel .|. cWBackPixel .|. etc ...
 
attrmask = cWOverrideRedirect .|. cWBorderPixel .|. cWBackPixel .|. etc ...
 
 
</haskell>
 
</haskell>
   
and set each of this attributes within allocaSetWindowAttributes with
+
and set each of this attributes within allocaSetWindowAttributes with [http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Misc.html#12 specific attributes setting functions].
[http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Misc.html#12 specific attributes setting functions].
 
   
Among these functions the one we need:
+
Among these functions the one we need: [http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Misc.html#v%3Aset_override_redirect set_override_redirect], whose type is:
[http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Misc.html#v%3Aset_override_redirect set_override_redirect],
 
whose type is:
 
   
 
<haskell>
 
<haskell>
 
 
set_override_redirect :: Ptr SetWindowAttributes -> Bool -> IO ()
 
set_override_redirect :: Ptr SetWindowAttributes -> Bool -> IO ()
 
 
</haskell>
 
</haskell>
   
 
This function takes the pointer to the XSetWindowAttributes structure and the flag to be set (True or False).
 
This function takes the pointer to the XSetWindowAttributes structure and the flag to be set (True or False).
   
For the list of avaliable attributes see
+
For the list of available attributes see
[http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Types.html#t%3AAttributeMask the AttributeMask type defnition].
+
[http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Types.html#t%3AAttributeMask
  +
the AttributeMask type definition].
   
For their meaning see
+
For their meaning see
[http://www.tronche.com/gui/x/xlib/window/attributes/#XSetWindowAttributes the XSetWindowAttributes structure reference].
+
[http://www.tronche.com/gui/x/xlib/window/attributes/#XSetWindowAttributes the
  +
XSetWindowAttributes structure reference].
   
Now, our goal was to create a window that the Window Manager is going
+
Now, our goal was to create a window that the window manager is going to ignore, and in order to do that all we need to set the attribute [http://www.tronche.com/gui/x/xlib/window/attributes/override-redirect.html
to ignore, and in order to do that all we need to set the attribute
+
CWOverrideRedirect] to True. And now we know how to do it.
[http://www.tronche.com/gui/x/xlib/window/attributes/override-redirect.html CWOverrideRedirect]
 
to True. And now we know how to do it.
 
   
Ok, it's time to introduce our function to create new windows with the CWOverrideRedirect set to True
+
Okay, it's time to introduce our function to create new windows with the CWOverrideRedirect set to True
   
 
<haskell>
 
<haskell>
 
 
mkUnmanagedWindow :: Display
 
mkUnmanagedWindow :: Display
-> Screen
+
-> Screen
-> Window
+
-> Window
-> Position
+
-> Position
-> Position
+
-> Position
-> Dimension
+
-> Dimension
-> Dimension
+
-> Dimension
-> IO Window
+
-> IO Window
 
mkUnmanagedWindow dpy scr rw x y w h = do
 
mkUnmanagedWindow dpy scr rw x y w h = do
let visual = defaultVisualOfScreen scr
+
let visual = defaultVisualOfScreen scr
attrmask = cWOverrideRedirect
+
attrmask = cWOverrideRedirect
win <- allocaSetWindowAttributes $
+
win <- allocaSetWindowAttributes $
\attributes -> do
+
\attributes -> do
set_override_redirect attributes True
+
set_override_redirect attributes True
createWindow dpy rw x y w h 0 (defaultDepthOfScreen scr)
+
createWindow dpy rw x y w h 0 (defaultDepthOfScreen scr)
inputOutput visual attrmask attributes
+
inputOutput visual attrmask attributes
return win
+
return win
 
 
</haskell>
 
</haskell>
   
Like simpleCreateWindow, our function is a wrapper around
+
Like simpleCreateWindow, our function is a wrapper around createWindow, but this time we are manually setting the CWOverrideRedirect flag.
createWindow, but this time we are manually setting the
 
CWOverrideRedirect flag.
 
   
As you see our function, unlike createSimpleWindow, does not have,
+
As you see our function, unlike createSimpleWindow, does not have, among its arguments, the background and the border pixels. This colors can be set, for windows created with createWindow, using the attribute mask, and setting CWBackPixel and CWBorderPixel with the needed functions:
among its arguments, the background and the border pixels. This colors
+
[http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Misc.html#v%3Aset_background_pixel set_background_pixel] and
can be set, for windows created with createWindow, using the attribute
+
[http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Misc.html#v%3Aset_border_pixel
mask, and setting CWBackPixel and CWBorderPixel with the needed
+
set_border_pixel].
functions:
 
[http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Misc.html#v%3Aset_background_pixel set_background_pixel]
 
and
 
[http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Misc.html#v%3Aset_border_pixel set_border_pixel].
 
   
By the way, setting the border color with this version of
+
By the way, setting the border color with this version of mkUnmanagedWindow is actually useless since the border width is set to zero. In the next example we will set it to 1.
mkUnmanagedWindow is actually useless since the border width is set to
 
zero. In the next example we will set it to 1.
 
   
Our function needs also the screen now, since we have to retrieve the
+
Our function needs also the screen now, since we have to retrieve the default depth and visual.
default depth and visual.
 
   
 
We can now rewrite our initial code using the new function now.
 
We can now rewrite our initial code using the new function now.
   
 
<haskell>
 
<haskell>
 
 
module Main where
 
module Main where
 
import Data.Bits
 
import Data.Bits
Line 248: Line 255:
 
main :: IO ()
 
main :: IO ()
 
main =
 
main =
do dpy <- openDisplay ""
+
do dpy <- openDisplay ""
let dflt = defaultScreen dpy
+
let dflt = defaultScreen dpy
scr = defaultScreenOfDisplay dpy
+
scr = defaultScreenOfDisplay dpy
rootw <- rootWindow dpy dflt
+
rootw <- rootWindow dpy dflt
win <- mkUnmanagedWindow dpy scr rootw 0 0 100 100
+
win <- mkUnmanagedWindow dpy scr rootw 0 0 100 100
setTextProperty dpy win "Hello World" wM_NAME
+
setTextProperty dpy win "Hello World" wM_NAME
mapWindow dpy win
+
mapWindow dpy win
sync dpy False
+
sync dpy False
threadDelay (10 * 1000000)
+
threadDelay (10 * 1000000)
exitWith ExitSuccess
+
exitWith ExitSuccess
   
 
mkUnmanagedWindow :: Display
 
mkUnmanagedWindow :: Display
-> Screen
+
-> Screen
-> Window
+
-> Window
-> Position
+
-> Position
-> Position
+
-> Position
-> Dimension
+
-> Dimension
-> Dimension
+
-> Dimension
-> IO Window
+
-> IO Window
 
mkUnmanagedWindow dpy scr rw x y w h = do
 
mkUnmanagedWindow dpy scr rw x y w h = do
let visual = defaultVisualOfScreen scr
+
let visual = defaultVisualOfScreen scr
attrmask = cWOverrideRedirect .|. cWBorderPixel .|. cWBackPixel
+
attrmask = cWOverrideRedirect .|. cWBorderPixel .|. cWBackPixel
win <- allocaSetWindowAttributes $
+
win <- allocaSetWindowAttributes $
\attributes -> do
+
\attributes -> do
set_override_redirect attributes True
+
set_override_redirect attributes True
set_background_pixel attributes $ whitePixel dpy (defaultScreen dpy)
+
set_background_pixel attributes $ whitePixel dpy (defaultScreen dpy)
set_border_pixel attributes $ blackPixel dpy (defaultScreen dpy)
+
set_border_pixel attributes $ blackPixel dpy (defaultScreen dpy)
createWindow dpy rw x y w h 1 (defaultDepthOfScreen scr)
+
createWindow dpy rw x y w h 1 (defaultDepthOfScreen scr)
inputOutput visual attrmask attributes
+
inputOutput visual attrmask attributes
return win
+
return win
 
 
</haskell>
 
</haskell>
   
Ok, let's give it a try. Did you see? Now the window will be placed in
+
Okay, let's give it a try. Did you see? Now the window will be placed in the specified x and y position, with the given dimensions. No window manager decorations, and so, no name displayed.
the specified x and y position, with the given dimensions. No Window
 
Manager decorations, and so, no name displayed.
 
   
 
===Changing an existing window's attributes===
 
===Changing an existing window's attributes===
  +
This task requires [http://www.tronche.com/gui/x/xlib/window/XChangeWindowAttributes.html
  +
XChangeWindowAttrbutes()], implemented only in the darcs version of
  +
[http://hackage.haskell.org/cgi-bin/hackage-scripts/package/X11-extras
  +
X11-extras].
   
This task requires
+
In order to change a window's attributes we just need the window ID in that specific X server, after that we need to unmap the window first, and then change its attributes with changeWindowAttributes, the interface to XChangeWindowAttrbutes() implemented by the darcs version of X11-extras.
[http://www.tronche.com/gui/x/xlib/window/XChangeWindowAttributes.html XChangeWindowAttrbutes()],
 
implemented only in the darcs version of
 
[http://hackage.haskell.org/cgi-bin/hackage-scripts/package/X11-extras X11-extras].
 
 
In order to change a window's attributes we just need the window ID in
 
that specific X server, after that we need to unmap the window first,
 
and then change its attributes with changeWindowAttributes, the
 
interface to XChangeWindowAttrbutes() implemented by
 
[http://darcs.haskell.org/~sjanssen/X11-extras the darcs version of X11-extras].
 
   
 
Here's the code:
 
Here's the code:
   
 
<haskell>
 
<haskell>
 
 
module Main where
 
module Main where
   
Line 301: Line 311:
 
main :: IO ()
 
main :: IO ()
 
main = do
 
main = do
args <- getArgs
+
args <- getArgs
pn <- getProgName
+
pn <- getProgName
let (win,ac) = case args of
+
let (win,ac) = case args of
[] -> error $ usage pn
+
[] -> error $ usage pn
w -> case (w !!0) of
+
w -> case (w !!0) of
"manage" -> (window, False)
+
"manage" -> (window, False)
"unmanage" -> (window, True)
+
"unmanage" -> (window, True)
_ -> error $ usage pn
+
_ -> error $ usage pn
where window = case (w !! 1) of
+
where window = case (w !! 1) of
[] -> error $ usage pn
+
[] -> error $ usage pn
w -> read w :: Window
+
w -> read w :: Window
dpy <- openDisplay ""
+
dpy <- openDisplay ""
unmapWindow dpy win
+
unmapWindow dpy win
sync dpy False
+
sync dpy False
allocaSetWindowAttributes $
+
allocaSetWindowAttributes $
\attributes -> do
+
\attributes -> do
set_override_redirect attributes ac
+
set_override_redirect attributes ac
changeWindowAttributes dpy win cWOverrideRedirect attributes
+
changeWindowAttributes dpy win cWOverrideRedirect attributes
mapWindow dpy win
+
mapWindow dpy win
sync dpy False
+
sync dpy False
 
 
</haskell>
 
</haskell>
   
Line 336: Line 346:
 
Obviously the important part of the code is this:
 
Obviously the important part of the code is this:
 
<haskell>
 
<haskell>
dpy <- openDisplay ""
+
dpy <- openDisplay ""
unmapWindow dpy win
+
unmapWindow dpy win
sync dpy False
+
sync dpy False
allocaSetWindowAttributes $
+
allocaSetWindowAttributes $
\attributes -> do
+
\attributes -> do
set_override_redirect attributes ac
+
set_override_redirect attributes ac
changeWindowAttributes dpy win cWOverrideRedirect attributes
+
changeWindowAttributes dpy win cWOverrideRedirect attributes
mapWindow dpy win
+
mapWindow dpy win
sync dpy False
+
sync dpy False
 
</haskell>
 
</haskell>
   
Line 351: Line 361:
 
# unmap the window
 
# unmap the window
 
# flush the output buffer to have the X server actually unmap the window
 
# flush the output buffer to have the X server actually unmap the window
# change the attributes with the same procedure we used to set them when creating the window
+
# change the attributes with the same procedure we used to set them when creating
  +
the window
 
# map the window again
 
# map the window again
 
# flush the output buffer to see the change take effect.
 
# flush the output buffer to see the change take effect.
Line 358: Line 368:
   
 
==Colors and color Ddepth==
 
==Colors and color Ddepth==
  +
So far we have set the window background color as a window attribute. This is not the most convenient way to set the window background color: if we need to change it, we must change the window's attribute, and we have seen that this task requires unmapping the window, flushing the output with changeWindowAttributes within changeWindowAttributes, remapping the window and reflushing the output
  +
buffer. Moreover we can do that only with the darcs version of X11-extras...
   
So far we have set the window background color as a window attribute.
+
In the following sections we are going to adopt a more efficient way of setting the window's background color: we will start drawing into the window. But first we must familiarize with colors and the way the X server deals with them.
This is not the most convenient way to set the window background
 
color: if we need to change it, we must change the window's attribute,
 
and we have seen that this task requires unmapping the window,
 
flushing the output with changeWindowAttributes within
 
changeWindowAttributes, remapping the window and reflushing the output
 
buffer. Moreover we can do that only we the darcs version of
 
X11-extras...
 
   
In the following sections we are going to adopt a more efficient way
+
So far we have set the colors by using some functions to retrieve their pixel values: blackPixel and whitePixel. These functions take the display and the default screen and return respectively the pixel values for the black and the white colors in that screen.
of setting the window's background color: we will start drawing into
 
the window. But first we must familiarize with colors and the way the
 
X server deals with them.
 
   
So far we have set the colors by using some functions to retrieve
+
A color is represented by a 32-bit unsigned integer, called a pixel value. The elements affecting the pixel representation of a color are:
their pixel values: blackPixel and whitePixel. These functions take
+
#the color depth;
the display and the default screen and return respectively the pixel
+
#the colormap, which is a table containing red, green, and blue intensity values;
values for the black and the white colors in that screen.
+
#the visual type.
   
A color is represented by a 32-bit unsigned integer, called a pixel
+
All these elements are specific to a given piece of hardware, and so our X application must detect them in order to set colors appropriately for that given hardware.
value. The elements affecting the pixel representation of a color are:
 
1. the color depth; 2. the colormap, which is a table containing red,
 
green, and blue intensity values; 3. the visual type.
 
   
All these elements are specific to a given piece of hardware, and so
+
The approach we are going to use to accomplish this task is this: we are going to use named colors, or colors represented by [http://en.wikipedia.org/wiki/RGB RGB triple], such as "red", "yellow", or "#FFFFFF", etc; and we are going to translate these colors into the pixel values appropriate for the screen we are operating on.
our X application must detect them in order to set colors
 
appropriately for that given hardware.
 
   
The approach we are going to use to accomplish this task is this: we
+
In order to achieve our goal we are going to use the function
are going to use named colors, or colors represented by
+
[http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Color.html#v%3AallocNamedColor
[http://en.wikipedia.org/wiki/RGB RGB triple], such as "red",
+
allocNamedColor]
"yellow", or "#FFFFFF", etc; and we are going to translate these
+
which is the interface to the X11 library function
colors into the pixel values appropriate for the screen we are
+
[http://www.tronche.com/gui/x/xlib/color/XAllocNamedColor.html
operating on.
+
XAllocNamedColor()].
 
In order to achieve our goal we are going to use the function
 
[http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Color.html#v%3AallocNamedColor allocNamedColor]
 
which is the interface to the X11 library function
 
[http://www.tronche.com/gui/x/xlib/color/XAllocNamedColor.html XAllocNamedColor()].
 
   
 
The type signature of allocNamedColor is:
 
The type signature of allocNamedColor is:
   
 
<haskell>
 
<haskell>
 
 
allocNamedColor :: Display -> Colormap -> String -> IO (Color, Color)
 
allocNamedColor :: Display -> Colormap -> String -> IO (Color, Color)
 
 
</haskell>
 
</haskell>
   
That is to say, given a display connection, a color map and a string -
+
That is to say, given a display connection, a color map and a string - our color representation -, this function will return a tuple with the closest RGB values provided by the hardware and the exact RGB values, both encoded in a
our color representation -, this function will return a tuple with the
+
[http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib.html#v%3AColor
closest RGB values provided by the hardware and the exact RGB values,
+
Haskell Color data type]. We will use the first approximated value.
both encoded in a
 
[http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib.html#v%3AColor Haskell Color data type].
 
We will use the first approximated value.
 
   
The Color data type has a field name we will use to retrieve the
+
The Color data type has a field name we will use to retrieve the needed pixel value: [http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib.html#v%3Acolor_pixel
needed pixel value:
+
color_pixel].
[http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib.html#v%3Acolor_pixel color_pixel].
 
   
 
We can now write this helper function:
 
We can now write this helper function:
 
 
<haskell>
 
<haskell>
 
 
initColor :: Display -> String -> IO Pixel
 
initColor :: Display -> String -> IO Pixel
 
initColor dpy color = do
 
initColor dpy color = do
let colormap = defaultColormap dpy (defaultScreen dpy)
+
let colormap = defaultColormap dpy (defaultScreen dpy)
(approx,real) <- allocNamedColor dpy colormap color
+
(approx,real) <- allocNamedColor dpy colormap color
return $ color_pixel approx
+
return $ color_pixel approx
 
 
</haskell>
 
</haskell>
   
To retrieve the colormap of the screen we used
+
To retrieve the colormap of the screen we used [http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Display.html#v%3AdefaultColormap
[http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Display.html#v%3AdefaultColormap defaultColormap],
+
defaultColormap], the interface to the X11 library function
the interface to the X11 library function
+
[http://www.tronche.com/gui/x/xlib/display/display-macros.html#DefaultColormap
[http://www.tronche.com/gui/x/xlib/display/display-macros.html#DefaultColormap XDefaultColormap()],
+
XDefaultColormap()], which requires the display and the screen, and returns the colormap of that screen.
which requires the display and the screen, and returns the colormap of that screen.
 
   
We can now rewrite our example using this new approach.
+
We can now rewrite our example using this new approach:
   
 
<haskell>
 
<haskell>
 
 
module Main where
 
module Main where
 
import Data.Bits
 
import Data.Bits
Line 422: Line 429:
 
main :: IO ()
 
main :: IO ()
 
main =
 
main =
do dpy <- openDisplay ""
+
do dpy <- openDisplay ""
let dflt = defaultScreen dpy
+
let dflt = defaultScreen dpy
scr = defaultScreenOfDisplay dpy
+
scr = defaultScreenOfDisplay dpy
rootw <- rootWindow dpy dflt
+
rootw <- rootWindow dpy dflt
win <- mkUnmanagedWindow dpy scr rootw 0 0 100 100
+
win <- mkUnmanagedWindow dpy scr rootw 0 0 100 100
setTextProperty dpy win "Hello World" wM_NAME
+
setTextProperty dpy win "Hello World" wM_NAME
mapWindow dpy win
+
mapWindow dpy win
sync dpy False
+
sync dpy False
threadDelay (10 * 1000000)
+
threadDelay (10 * 1000000)
exitWith ExitSuccess
+
exitWith ExitSuccess
   
 
mkUnmanagedWindow :: Display
 
mkUnmanagedWindow :: Display
-> Screen
+
-> Screen
-> Window
+
-> Window
-> Position
+
-> Position
-> Position
+
-> Position
-> Dimension
+
-> Dimension
-> Dimension
+
-> Dimension
-> IO Window
+
-> IO Window
 
mkUnmanagedWindow dpy scr rw x y w h = do
 
mkUnmanagedWindow dpy scr rw x y w h = do
let visual = defaultVisualOfScreen scr
+
let visual = defaultVisualOfScreen scr
attrmask = cWOverrideRedirect .|. cWBorderPixel .|. cWBackPixel
+
attrmask = cWOverrideRedirect .|. cWBorderPixel .|. cWBackPixel
background_color <- initColor dpy "red"
+
background_color <- initColor dpy "red"
border_color <- initColor dpy "black"
+
border_color <- initColor dpy "black"
win <- allocaSetWindowAttributes $
+
win <- allocaSetWindowAttributes $
\attributes -> do
+
\attributes -> do
set_override_redirect attributes True
+
set_override_redirect attributes True
set_background_pixel attributes background_color
+
set_background_pixel attributes background_color
set_border_pixel attributes border_color
+
set_border_pixel attributes border_color
createWindow dpy rw x y w h 1 (defaultDepthOfScreen scr)
+
createWindow dpy rw x y w h 1 (defaultDepthOfScreen scr)
inputOutput visual attrmask attributes
+
inputOutput visual attrmask attributes
return win
+
return win
 
   
 
initColor :: Display -> String -> IO Pixel
 
initColor :: Display -> String -> IO Pixel
 
initColor dpy color = do
 
initColor dpy color = do
let colormap = defaultColormap dpy (defaultScreen dpy)
+
let colormap = defaultColormap dpy (defaultScreen dpy)
(apros,real) <- allocNamedColor dpy colormap color
+
(apros,real) <- allocNamedColor dpy colormap color
return $ color_pixel apros
+
return $ color_pixel apros
 
 
</haskell>
 
</haskell>
   
Just give it a try. Now you can also experiment with different colors.
+
Just give it a try. Now you can also experiment with different colors. This approach will assure that our application will work no matter the color depth of the screen we are working on.
This approach will assure that our application will work no matter the
 
color depth of the screen we are working on.
 
   
 
==Drawing in windows==
 
==Drawing in windows==
 
 
The X server provides two objects that can be used to draw something
 
The X server provides two objects that can be used to draw something
 
to: windows and pixmaps.
 
to: windows and pixmaps.
Line 471: Line 477:
 
In this section we will start drawing into windows.
 
In this section we will start drawing into windows.
   
We have seen that changing the background color of a window is a
+
We have seen that changing the background color of a window is a troublesome operation, since the window must be unmapped and remapped, memory for a foreign structure allocated, and so on.
troublesome operation, since the window must be unamapped and
 
remapped, memory for a foreign structure allocated, and so on.
 
   
Instead, we can use some graphic operations to draw a rectangle over
+
Instead, we can use some graphic operations to draw a rectangle over the window. We will latter manipulate the foreground, visible, color of this rectangle, that will become the new background of our window.
the window. We will latter manipulate the foreground, visible, color of
 
this rectangle, that will become the new background of our window.
 
   
We can also use multiple rectangles with different dimension to
+
We can also use multiple rectangles with different dimension to decorate our window with a border, for instance. Later on we will print some text over these rectangles.
decorate our window with a border, for instance.
 
 
Later on we will print some text over these rectangles.
 
   
 
===Drawing rectangles in a window===
 
===Drawing rectangles in a window===
 
 
Citing from
 
Citing from
[http://en.wikipedia.org/wiki/X_Window_System_core_protocol#Graphic_contexts_and_fonts Wikipedia]:
+
[http://en.wikipedia.org/wiki/X_Window_System_core_protocol#Graphic_contexts_and_fonts
  +
Wikipedia]:
   
<blockquote>
+
<blockquote> The client can request a number of graphic operations, such clearing an area, copying an area into another, drawing points, lines, rectangles, and text. Beside clearing, all operations are possible on all drawables, both windows and pixmaps.
The client can request a number of graphic operations, such clearing
 
an area, copying an area into another, drawing points, lines,
 
rectangles, and text. Beside clearing, all operations are possible on
 
all drawables, both windows and pixmaps.
 
   
Most requests for graphic operations include a graphic context, which
+
Most requests for graphic operations include a graphic context, which is a structure that contains the parameters of the graphic operations. A graphic context includes the foreground color, the background color, the font of text, and other graphic parameters. When requesting a graphic operation, the client includes a graphic context. </blockquote>
is a structure that contains the parameters of the graphic operations.
 
A graphic context includes the foreground color, the background color,
 
the font of text, and other graphic parameters. When requesting a
 
graphic operation, the client includes a graphic context.
 
</blockquote>
 
   
In other words, as for setting window's attribute, we must use a
+
In other words, as for setting window's attribute, we must use a foreign C structure to set parameters for graphic operations, and then we will feed this structure to the [http://www.tronche.com/gui/x/xlib/graphics/ functions]
foreign C structure to set parameters for graphic operations, and then
 
we will feed this structure to the
 
[http://www.tronche.com/gui/x/xlib/graphics/ functions]
 
 
that will perform these graphic operations.
 
that will perform these graphic operations.
   
We one difference: instead of operating within a function that
+
There is one difference: instead of operating within a function that allocates memory and creates a pointer to the foreign structure, now we have to explicitly create the Graphic Context, and free it after having used it, with
allocates memory and creates a pointer to the foreign structure, now
+
[http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Context.html#v%3AcreateGC
we have to explicitally create the Graphic Context, and free it after
+
createGC], the interface to [http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Context.html#v%3AcreateGC
having used it, with
+
XCreateGC], and
[http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Context.html#v%3AcreateGC createGC],
+
[http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Context.html#v%3AfreeGC
the interface to
+
freeGC], the interface to
[http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Context.html#v%3AcreateGC XCreateGC],
 
and
 
[http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Context.html#v%3AfreeGC freeGC],
 
the interface to
 
 
[http://www.tronche.com/gui/x/xlib/GC/XFreeGC.html XFreeGC].
 
[http://www.tronche.com/gui/x/xlib/GC/XFreeGC.html XFreeGC].
   
Be careful: if you create a graphic context without freeing it after
+
Be careful: if you create a graphic context without freeing it after use, you are going to end up with a sizeable memory leak!
use, you are going to end up with a noticeable memory leak!
 
   
The specific graphic functions we are going to need for drawing a
+
The specific graphic functions we are going to need for drawing a rectangle into our window are:
rectangle into our window are:
 
   
# [http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Context.html#v%3AsetForeground setForegrond] the interface to [http://www.tronche.com/gui/x/xlib/GC/convenience-functions/XSetForeground.html XSetForegroung]
+
#
# [http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Context.html#v%3AsetBackground setBackground] the interface to [http://www.tronche.com/gui/x/xlib/GC/convenience-functions/XSetBackground.html XSetBackground]
+
[http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Context.html#v%3AsetForeground setForegrond] the interface to [http://www.tronche.com/gui/x/xlib/GC/convenience-functions/XSetForeground.html
# [http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Misc.html#v%3AfillRectangle fillRectangle] the interface to [http://www.tronche.com/gui/x/xlib/graphics/filling-areas/XFillRectangle.html XFillRectangle]
+
XSetForegroung]
  +
#
  +
[http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Context.html#v%3AsetBackground
  +
setBackground] the interface to
  +
[http://www.tronche.com/gui/x/xlib/GC/convenience-functions/XSetBackground.html
  +
XSetBackground]
  +
#
  +
[http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Misc.html#v%3AfillRectangle
  +
fillRectangle] the interface to
  +
[http://www.tronche.com/gui/x/xlib/graphics/filling-areas/XFillRectangle.html
  +
XFillRectangle]
   
The first two functions are needed to set the parameters in the
+
The first two functions are needed to set the parameters in the [http://www.tronche.com/gui/x/xlib/GC/manipulating.html Graphic Context]. The third one will use this GC for filling a rectangle on the specified window. Just have a look to their type signatures:
[http://www.tronche.com/gui/x/xlib/GC/manipulating.html Graphic Context].
 
The third one will use this GC for filling a rectangle on the
 
specified window. Just have a look to their type signatures:
 
   
 
<haskell>
 
<haskell>
 
 
setForeground :: Display -> GC -> Pixel -> IO ()
 
setForeground :: Display -> GC -> Pixel -> IO ()
 
setBackground :: Display -> GC -> Pixel -> IO ()
 
setBackground :: Display -> GC -> Pixel -> IO ()
fillRectangle :: Display -> Drawable -> GC -> Position -> Position -> Dimension -> Dimension -> IO ()
+
fillRectangle :: Display -> Drawable -> GC -> Position -> Position -> Dimension ->
+
Dimension -> IO ()
 
</haskell>
 
</haskell>
   
Ok, this is the function that we will be using for drawing into a
+
Okay, this is the function that we will be using for drawing into a window:
window:
 
   
 
<haskell>
 
<haskell>
 
 
drawInWin :: Display -> Window -> IO ()
 
drawInWin :: Display -> Window -> IO ()
 
drawInWin dpy win = do
 
drawInWin dpy win = do
fgcolor <- initColor dpy "blue"
+
fgcolor <- initColor dpy "blue"
gc <- createGC dpy win
+
gc <- createGC dpy win
setForeground dpy gc fgcolor
+
setForeground dpy gc fgcolor
fillRectangle dpy win gc 0 0 100 100
+
fillRectangle dpy win gc 0 0 100 100
freeGC dpy gc
+
freeGC dpy gc
 
 
</haskell>
 
</haskell>
   
This will just fill our window with a rectangle at (0, 0) (x, y)
+
This will just fill our window with a rectangle at (0, 0) (''x'', ''y'') coordinates (relatives to the window's internal border), with the same dimensions of our window.
coordinates (relatives to the window's internal border), with the same
 
dimensions of our window.
 
   
Obviously we can play a bit with rectangles. This version, for
+
Obviously we can play a bit with rectangles. This version, for instance, will draw 2 rectangles to simulate a blue rectangle with a green border, two pixels width:
instance, will draw 2 rectangles to simulate a blu rectangle with a
 
green border, two pixels width:
 
   
 
<haskell>
 
<haskell>
 
 
drawInWin :: Display -> Window -> IO ()
 
drawInWin :: Display -> Window -> IO ()
 
drawInWin dpy win = do
 
drawInWin dpy win = do
bgcolor <- initColor dpy "green"
+
bgcolor <- initColor dpy "green"
fgcolor <- initColor dpy "blue"
+
fgcolor <- initColor dpy "blue"
gc <- createGC dpy win
+
gc <- createGC dpy win
setForeground dpy gc bgcolor
+
setForeground dpy gc bgcolor
fillRectangle dpy win gc 0 0 100 100
+
fillRectangle dpy win gc 0 0 100 100
setForeground dpy gc fgcolor
+
setForeground dpy gc fgcolor
fillRectangle dpy win gc 2 2 96 96
+
fillRectangle dpy win gc 2 2 96 96
freeGC dpy gc
+
freeGC dpy gc
 
 
</haskell>
 
</haskell>
   
You can use this function on a mapped window. This is our original
+
You can use this function on a mapped window. This is our original example, rewritten with this approach:
example rewritten with this new approach:
 
   
 
<haskell>
 
<haskell>
 
 
module Main where
 
module Main where
 
import Data.Bits
 
import Data.Bits
Line 558: Line 559:
 
main :: IO ()
 
main :: IO ()
 
main =
 
main =
do dpy <- openDisplay ""
+
do dpy <- openDisplay ""
let dflt = defaultScreen dpy
+
let dflt = defaultScreen dpy
scr = defaultScreenOfDisplay dpy
+
scr = defaultScreenOfDisplay dpy
rootw <- rootWindow dpy dflt
+
rootw <- rootWindow dpy dflt
win <- mkUnmanagedWindow dpy scr rootw 0 0 100 100
+
win <- mkUnmanagedWindow dpy scr rootw 0 0 100 100
setTextProperty dpy win "Hello World" wM_NAME
+
setTextProperty dpy win "Hello World" wM_NAME
mapWindow dpy win
+
mapWindow dpy win
drawInWin dpy win
+
drawInWin dpy win
sync dpy False
+
sync dpy False
threadDelay (10 * 1000000)
+
threadDelay (10 * 1000000)
exitWith ExitSuccess
+
exitWith ExitSuccess
   
 
drawInWin :: Display -> Window -> IO ()
 
drawInWin :: Display -> Window -> IO ()
 
drawInWin dpy win = do
 
drawInWin dpy win = do
bgcolor <- initColor dpy "green"
+
bgcolor <- initColor dpy "green"
fgcolor <- initColor dpy "blue"
+
fgcolor <- initColor dpy "blue"
gc <- createGC dpy win
+
gc <- createGC dpy win
setForeground dpy gc bgcolor
+
setForeground dpy gc bgcolor
fillRectangle dpy win gc 0 0 100 100
+
fillRectangle dpy win gc 0 0 100 100
setForeground dpy gc fgcolor
+
setForeground dpy gc fgcolor
fillRectangle dpy win gc 2 2 96 96
+
fillRectangle dpy win gc 2 2 96 96
freeGC dpy gc
+
freeGC dpy gc
   
 
mkUnmanagedWindow :: Display
 
mkUnmanagedWindow :: Display
-> Screen
+
-> Screen
-> Window
+
-> Window
-> Position
+
-> Position
-> Position
+
-> Position
-> Dimension
+
-> Dimension
-> Dimension
+
-> Dimension
-> IO Window
+
-> IO Window
 
mkUnmanagedWindow dpy scr rw x y w h = do
 
mkUnmanagedWindow dpy scr rw x y w h = do
let visual = defaultVisualOfScreen scr
+
let visual = defaultVisualOfScreen scr
win <- allocaSetWindowAttributes $
+
win <- allocaSetWindowAttributes $
\attributes -> do
+
\attributes -> do
set_override_redirect attributes True
+
set_override_redirect attributes True
createWindow dpy rw x y w h 0 (defaultDepthOfScreen scr)
+
createWindow dpy rw x y w h 0 (defaultDepthOfScreen scr)
inputOutput visual cWOverrideRedirect attributes
+
inputOutput visual cWOverrideRedirect attributes
return win
+
return win
 
   
 
initColor :: Display -> String -> IO Pixel
 
initColor :: Display -> String -> IO Pixel
 
initColor dpy color = do
 
initColor dpy color = do
 
 
</haskell>
 
</haskell>
   
As you see, now mkUnmanagedWindow sets a null border width and does
+
As you see, now mkUnmanagedWindow sets a null border width and does not set any background color. Everything is easily done with rectangles.
not set any background color. Everything is easily done with
 
rectangles.
 
   
 
===Printing a string===
 
===Printing a string===
  +
Printing a string to a window requires operating with another foreign C structure, the [http://www.tronche.com/gui/x/xlib/graphics/font-metrics/ XFontStruct], which contains all of the information regarding the metrics of font that the X server will use to display our string.
   
Printing a string to a window requires operating with another foreign
+
This structure will be used to perform some computations that are required for the correct placement of the text in the window.
C structure, the
 
[http://www.tronche.com/gui/x/xlib/graphics/font-metrics/ XFontStruct],
 
which contains all of the information regarding the metrics of font
 
that the X server will use to display our string.
 
   
This structure will be used to perform some computations that are
+
As we have seen with the window's attributes and the Graphic Context, we need a function that returns a pointer to this foreign structure, pointer that must be freed after using it.
required for the correct placement of the text in the window.
 
   
As we have seen with the window's attributes and the Graphic Context,
+
A pointer to the XFontStruct is returned by [http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Font.html#v%3AloadQueryFont
we need a function that returns a pointer to this foreign structure,
+
loadQueryFont], the interface to the X11 library function
pointer that must be freed after using it.
+
[http://www.tronche.com/gui/x/xlib/graphics/font-metrics/XLoadQueryFont.html
  +
XLoadQueryFont()].
   
A pointer to the XFontStruct is returned by
+
XLoadQueryFont, which requires the X connection and the font name, will perform two distinct operations: load the needed font and return its id (performed by [http://www.tronche.com/gui/x/xlib/graphics/font-metrics/XLoadFont.html XLoadFont()] which doesn't have a Haskell interface) and query the font to retrieve the XFontStruct
[http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Font.html#v%3AloadQueryFont loadQueryFont],
+
(performed by
the interface to the X11 library function
+
[http://www.tronche.com/gui/x/xlib/graphics/font-metrics/XQueryFont.html
[http://www.tronche.com/gui/x/xlib/graphics/font-metrics/XLoadQueryFont.html XLoadQueryFont()].
+
XQueryFont] which does have a Haskell interface:
  +
[http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Font.html#v%3AqueryFont
  +
queryFont]).
   
XLoadQueryFont, which requires the X connection and the font name,
+
The XFontStruct is needed by
will perform two distinct operations: load the needed font and return
+
[http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Font.html#v%3AtextExtents
its id
+
textExtents], the interface to
(performed by [http://www.tronche.com/gui/x/xlib/graphics/font-metrics/XLoadFont.html XLoadFont()]
+
[http://www.tronche.com/gui/x/xlib/graphics/font-metrics/XTextExtents.html
which doesn't have a Haskell interface) and query the font to retrieve the XFontStruct
+
XTexteExtent()], and
(performed by [http://www.tronche.com/gui/x/xlib/graphics/font-metrics/XQueryFont.html XQueryFont]
+
[http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Font.html#v%3AtextWidth
which does have a Haskell interface:
+
textWidth], the interface to
[http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Font.html#v%3AqueryFont queryFont]).
+
[http://www.tronche.com/gui/x/xlib/graphics/font-metrics/XTextWidth.html
  +
XTextWidth()].
   
The XFontStruct is needed by
+
These are their type signatures:
[http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Font.html#v%3AtextExtents textExtents],
 
the interface to
 
[http://www.tronche.com/gui/x/xlib/graphics/font-metrics/XTextExtents.html XTexteExtent()],
 
and
 
[http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Font.html#v%3AtextWidth textWidth],
 
the interface to
 
[http://www.tronche.com/gui/x/xlib/graphics/font-metrics/XTextWidth.html XTextWidth()].
 
 
These are their type signatures.
 
   
 
<haskell>
 
<haskell>
 
 
textExtents :: FontStruct -> String -> (FontDirection, Int32, Int32, CharStruct)
 
textExtents :: FontStruct -> String -> (FontDirection, Int32, Int32, CharStruct)
 
textWidth :: FontStruct -> String -> Int32
 
textWidth :: FontStruct -> String -> Int32
Line 645: Line 645:
   
 
This information can be used with the graphic function that will
 
This information can be used with the graphic function that will
actually draw the text on the window:
+
actually draw the text on the window:
[http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Misc.html#v%3AdrawImageString drawImageString],
+
[http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Misc.html#v%3AdrawImageString
the interface to
+
drawImageString],
[http://www.tronche.com/gui/x/xlib/graphics/drawing-text/XDrawImageString.html XDrawImageString].
+
the interface to
  +
[http://www.tronche.com/gui/x/xlib/graphics/drawing-text/XDrawImageString.html
  +
XDrawImageString].
   
There are other version of this string,
+
There are other version of this string,
[http://www.tronche.com/gui/x/xlib/graphics/drawing-text/XDrawText.html XDrawText()]
+
[http://www.tronche.com/gui/x/xlib/graphics/drawing-text/XDrawText.html
and
+
XDrawText()]
[http://www.tronche.com/gui/x/xlib/graphics/drawing-text/XDrawText16.html XDrawText16()]
+
and
  +
[http://www.tronche.com/gui/x/xlib/graphics/drawing-text/XDrawText16.html
  +
XDrawText16()]
 
for 16-bit characters.
 
for 16-bit characters.
   
Line 659: Line 659:
 
foreground pixel set in the Graphic Context.
 
foreground pixel set in the Graphic Context.
   
It's type signature is:
+
Its type signature is:
   
 
<haskell>
 
<haskell>
+
drawImageString :: Display -> Drawable -> GC -> Position -> Position -> String ->
drawImageString :: Display -> Drawable -> GC -> Position -> Position -> String -> IO ()
+
IO ()
 
 
</haskell>
 
</haskell>
   
Finally we must remember to free the FontStruct with
+
Finally we must remember to free the FontStruct with
[http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Font.html#v%3AfreeFont freeFont]
+
[http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Font.html#v%3AfreeFont
the interface to
+
freeFont] the interface to
[http://www.tronche.com/gui/x/xlib/graphics/font-metrics/XFreeFont.html XFreeFont].
+
[http://www.tronche.com/gui/x/xlib/graphics/font-metrics/XFreeFont.html
  +
XFreeFont].
   
 
We can now write our function to print a string on a window, but first
 
We can now write our function to print a string on a window, but first
we need to make some small modifications to our darwInWin function,
+
we need to make some small modifications to our drawInWin function,
 
that will now take a string and will load and free the needed
 
that will now take a string and will load and free the needed
 
FontStruct to be passed to the new printString function:
 
FontStruct to be passed to the new printString function:
   
 
<haskell>
 
<haskell>
 
 
drawInWin :: Display -> Window -> String -> IO ()
 
drawInWin :: Display -> Window -> String -> IO ()
 
drawInWin dpy win str = do
 
drawInWin dpy win str = do
bgcolor <- initColor dpy "green"
+
bgcolor <- initColor dpy "green"
fgcolor <- initColor dpy "blue"
+
fgcolor <- initColor dpy "blue"
gc <- createGC dpy win
+
gc <- createGC dpy win
fontStruc <- loadQueryFont dpy "-misc-fixed-*-*-*-*-10-*-*-*-*-*-*-*"
+
fontStruc <- loadQueryFont dpy "-misc-fixed-*-*-*-*-10-*-*-*-*-*-*-*"
setForeground dpy gc bgcolor
+
setForeground dpy gc bgcolor
fillRectangle dpy win gc 0 0 200 100
+
fillRectangle dpy win gc 0 0 200 100
setForeground dpy gc fgcolor
+
setForeground dpy gc fgcolor
fillRectangle dpy win gc 2 2 196 96
+
fillRectangle dpy win gc 2 2 196 96
printString dpy win gc fontStruc str
+
printString dpy win gc fontStruc str
freeGC dpy gc
+
freeGC dpy gc
freeFont dpy fontStruc
+
freeFont dpy fontStruc
 
 
</haskell>
 
</haskell>
   
Here we are loading the "misc-fixed" font. You can select different
+
Here we are loading the "misc-fixed" font. You can select different fonts with the standalone utility:
fonts with the standalone utility:
 
 
xfontsel
 
xfontsel
   
As you see the FontStruct retrieved by loadQueryFont is used by
+
As you see the FontStruct retrieved by loadQueryFont is used by printString and then freed.
printString and then freed.
 
 
So, let's look printString:
 
   
  +
So, let's look at printString:
   
 
<haskell>
 
<haskell>
   
printString :: Display
+
printString :: Display
-> Drawable
+
-> Drawable
-> GC
+
-> GC
-> FontStruct
+
-> FontStruct
-> String
+
-> String
-> IO ()
+
-> IO ()
 
printString dpy d gc fontst str =
 
printString dpy d gc fontst str =
do let strLen = textWidth fontst str
+
do let strLen = textWidth fontst str
(_,asc,_,_) = textExtents fontst str
+
(_,asc,_,_) = textExtents fontst str
valign = (100 + fromIntegral asc) `div` 2
+
valign = (100 + fromIntegral asc) `div` 2
remWidth = 200 - strLen
+
remWidth = 200 - strLen
offset = remWidth `div` 2
+
offset = remWidth `div` 2
fgcolor <- initColor dpy "white"
+
fgcolor <- initColor dpy "white"
bgcolor <- initColor dpy "blue"
+
bgcolor <- initColor dpy "blue"
setForeground dpy gc fgcolor
+
setForeground dpy gc fgcolor
setBackground dpy gc bgcolor
+
setBackground dpy gc bgcolor
drawImageString dpy d gc offset valign str
+
drawImageString dpy d gc offset valign str
   
 
</haskell>
 
</haskell>
   
In the "let" part we use textWidth and textExtents to set vertical and
+
In the "let" part we use textWidth and textExtents to set vertical and horizontal alignment: this is done by calculating the ''x'' and ''y'' coordinates for drawImageString. In this example text will be vertically and horizontally centered (take into account that I have also enlarged the window, whose width now is 200 pixels).
horizontal alignment: this is done by calculating the x and y
 
coordinates for drawImageString. In this example text will be
 
vertically and horizontally centered (take into account that I have
 
also enlarged the window, whose width now is 200 pixels).
 
   
For a reference of the meaning of font ascent and descent, and the
+
For a reference of the meaning of font ascent and descent, and the origins of the rectangle drawn by drawImageString read the par. 8.6 ([http://www.tronche.com/gui/x/xlib/graphics/drawing-text/ Drawing Text])
origins of the rectangle drawn by drawImageString read the par. 8.6
+
of the [http://www.tronche.com/gui/x/xlib/ The Xlib Manual].
([http://www.tronche.com/gui/x/xlib/graphics/drawing-text/ Drawing Text])
 
of the
 
[http://www.tronche.com/gui/x/xlib/ The Xlib Manual].
 
   
You may notice that printString takes a
+
You may notice that printString takes a
[http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Types.html#t%3ADrawable Drawable],
+
[http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Types.html#t%3ADrawable
which can be either a window or a pixmap (see below).
+
Drawable], which can be either a window or a pixmap (see below).
   
We can now rewrite our example and finally see some text printed in
+
We can now rewrite our example and finally see some text printed in our window:
our window:
 
   
 
<haskell>
 
<haskell>
 
 
module Main where
 
module Main where
 
import Data.Bits
 
import Data.Bits
Line 743: Line 742:
 
main :: IO ()
 
main :: IO ()
 
main =
 
main =
do dpy <- openDisplay ""
+
do dpy <- openDisplay ""
let dflt = defaultScreen dpy
+
let dflt = defaultScreen dpy
scr = defaultScreenOfDisplay dpy
+
scr = defaultScreenOfDisplay dpy
rootw <- rootWindow dpy dflt
+
rootw <- rootWindow dpy dflt
win <- mkUnmanagedWindow dpy scr rootw 0 0 200 100
+
win <- mkUnmanagedWindow dpy scr rootw 0 0 200 100
setTextProperty dpy win "The Clock" wM_NAME
+
setTextProperty dpy win "The Clock" wM_NAME
mapWindow dpy win
+
mapWindow dpy win
drawInWin dpy win =<< date
+
drawInWin dpy win =<< date
sync dpy False
+
sync dpy False
threadDelay (10 * 1000000)
+
threadDelay (10 * 1000000)
exitWith ExitSuccess
+
exitWith ExitSuccess
   
 
date :: IO String
 
date :: IO String
date = do
+
date = do
t <- toCalendarTime =<< getClockTime
+
t <- toCalendarTime =<< getClockTime
return $ calendarTimeToString t
+
return $ calendarTimeToString t
   
 
drawInWin :: Display -> Window -> String -> IO ()
 
drawInWin :: Display -> Window -> String -> IO ()
 
drawInWin dpy win str = do
 
drawInWin dpy win str = do
bgcolor <- initColor dpy "green"
+
bgcolor <- initColor dpy "green"
fgcolor <- initColor dpy "blue"
+
fgcolor <- initColor dpy "blue"
gc <- createGC dpy win
+
gc <- createGC dpy win
fontStruc <- loadQueryFont dpy "-misc-fixed-*-*-*-*-10-*-*-*-*-*-*-*"
+
fontStruc <- loadQueryFont dpy "-misc-fixed-*-*-*-*-10-*-*-*-*-*-*-*"
setForeground dpy gc bgcolor
+
setForeground dpy gc bgcolor
fillRectangle dpy win gc 0 0 200 100
+
fillRectangle dpy win gc 0 0 200 100
setForeground dpy gc fgcolor
+
setForeground dpy gc fgcolor
fillRectangle dpy win gc 2 2 196 96
+
fillRectangle dpy win gc 2 2 196 96
printString dpy win gc fontStruc str
+
printString dpy win gc fontStruc str
freeGC dpy gc
+
freeGC dpy gc
freeFont dpy fontStruc
+
freeFont dpy fontStruc
   
printString :: Display
+
printString :: Display
-> Drawable
+
-> Drawable
-> GC
+
-> GC
-> FontStruct
+
-> FontStruct
-> String
+
-> String
-> IO ()
+
-> IO ()
 
printString dpy d gc fontst str =
 
printString dpy d gc fontst str =
do let strLen = textWidth fontst str
+
do let strLen = textWidth fontst str
(_,asc,_,_) = textExtents fontst str
+
(_,asc,_,_) = textExtents fontst str
valign = (100 + fromIntegral asc) `div` 2
+
valign = (100 + fromIntegral asc) `div` 2
remWidth = 200 - strLen
+
remWidth = 200 - strLen
offset = remWidth `div` 2
+
offset = remWidth `div` 2
fgcolor <- initColor dpy "white"
+
fgcolor <- initColor dpy "white"
bgcolor <- initColor dpy "blue"
+
bgcolor <- initColor dpy "blue"
setForeground dpy gc fgcolor
+
setForeground dpy gc fgcolor
setBackground dpy gc bgcolor
+
setBackground dpy gc bgcolor
drawImageString dpy d gc offset valign str
+
drawImageString dpy d gc offset valign str
   
 
mkUnmanagedWindow :: Display
 
mkUnmanagedWindow :: Display
-> Screen
+
-> Screen
-> Window
+
-> Window
-> Position
+
-> Position
-> Position
+
-> Position
-> Dimension
+
-> Dimension
-> Dimension
+
-> Dimension
-> IO Window
+
-> IO Window
 
mkUnmanagedWindow dpy scr rw x y w h = do
 
mkUnmanagedWindow dpy scr rw x y w h = do
let visual = defaultVisualOfScreen scr
+
let visual = defaultVisualOfScreen scr
win <- allocaSetWindowAttributes $
+
win <- allocaSetWindowAttributes $
\attributes -> do
+
\attributes -> do
set_override_redirect attributes True
+
set_override_redirect attributes True
createWindow dpy rw x y w h 0 (defaultDepthOfScreen scr)
+
createWindow dpy rw x y w h 0 (defaultDepthOfScreen scr)
inputOutput visual cWOverrideRedirect attributes
+
inputOutput visual cWOverrideRedirect attributes
return win
+
return win
 
   
 
initColor :: Display -> String -> IO Pixel
 
initColor :: Display -> String -> IO Pixel
 
initColor dpy color = do
 
initColor dpy color = do
let colormap = defaultColormap dpy (defaultScreen dpy)
+
let colormap = defaultColormap dpy (defaultScreen dpy)
(apros,real) <- allocNamedColor dpy colormap color
+
(apros,real) <- allocNamedColor dpy colormap color
return $ color_pixel apros
+
return $ color_pixel apros
 
 
</haskell>
 
</haskell>
   
Since we are going to display the system time, I already added a
+
Since we are going to display the system time, I already added a "date" function and increased to width of our window.
"date" function and increased to width of our window.
 
   
 
Just give it a try. We are almost there. It's a clock, after all.
 
Just give it a try. We are almost there. It's a clock, after all.
   
 
==Updating a window==
 
==Updating a window==
+
If you do not believe that now we have a system clock, just change the main function of the above example with the following two functions and
If you do not believe that now we have a system clock, just change the
 
main function of the above example with the following two functions and
 
 
try yourself:
 
try yourself:
   
 
<haskell>
 
<haskell>
 
 
main :: IO ()
 
main :: IO ()
main = do
+
main = do
dpy <- openDisplay ""
+
dpy <- openDisplay ""
let dflt = defaultScreen dpy
+
let dflt = defaultScreen dpy
scr = defaultScreenOfDisplay dpy
+
scr = defaultScreenOfDisplay dpy
rootw <- rootWindow dpy dflt
+
rootw <- rootWindow dpy dflt
win <- mkUnmanagedWindow dpy scr rootw 0 0 200 100
+
win <- mkUnmanagedWindow dpy scr rootw 0 0 200 100
setTextProperty dpy win "The Clock" wM_NAME
+
setTextProperty dpy win "The Clock" wM_NAME
mapWindow dpy win
+
mapWindow dpy win
updateWin dpy win
+
updateWin dpy win
   
 
updateWin :: Display -> Window -> IO ()
 
updateWin :: Display -> Window -> IO ()
 
updateWin dpy win = do
 
updateWin dpy win = do
drawInWin dpy win =<< date
+
drawInWin dpy win =<< date
sync dpy False
+
sync dpy False
threadDelay (1 * 1000000)
+
threadDelay (1 * 1000000)
updateWin dpy win
+
updateWin dpy win
 
 
</haskell>
 
</haskell>
   
Line 848: Line 846:
 
this loop, reduces the thread block to 1 second. That's it.
 
this loop, reduces the thread block to 1 second. That's it.
   
Every second our window will be updated - redrawn.
+
Every second our window will be updated (redrawn).
   
Now, if you let the clock run for a while, you will notice that
+
Now, if you let the clock run for a while, you will notice that sometimes, during an update, the window sort of flickers.
sometimes, during an update, the window sort of flickers.
 
   
This is due to the fact that we are drawing directly over the window.
+
This is due to the fact that we are drawing directly over the window. We need to adopt a better technique: we need to write to a pixmap first, and then copy the pixmap over the window.
We need to adopt a better technique: we need to write to a pixmap
 
first, and then copy the pixmap over the window.
 
   
 
Citing from
 
Citing from
[http://en.wikipedia.org/wiki/X_Window_System_core_protocol#Pixmaps_and_drawables Wikipedia]:
+
[http://en.wikipedia.org/wiki/X_Window_System_core_protocol#Pixmaps_and_drawables
  +
Wikipedia]:
   
<blockquote>
+
<blockquote>"A pixmap is a region of memory that can be used for drawing. Contrary to windows, the contents of pixmaps are not automatically shown on the screen. However, the content of a pixmap (or a part of it) can be transferred to a window and vice versa. This allows for techniques such as [http://en.wikipedia.org/wiki/Double_buffering double buffering].
«A pixmap is a region of memory that can be used for
+
Most of the graphical operations that can be done on windows can also be done on pixmaps. Windows and pixmaps are collectively named drawables, and their content data resides on the server."</blockquote>
drawing. Contrary to windows, the contents of pixmaps are not
 
automatically shown on the screen. However, the content of a pixmap
 
(or a part of it) can be transferred to a window and vice versa. This
 
allows for techniques such as
 
[http://en.wikipedia.org/wiki/Double_buffering double buffering].
 
Most of the graphical
 
operations that can be done on windows can also be done on pixmaps.
 
Windows and pixmaps are collectively named drawables, and their
 
content data resides on the server.»
 
</blockquote>
 
   
This is not very difficult, and requires a very small change of our
+
This is not very difficult, and requires a very small change of our drawInWin function.
drawInWin function.
 
   
In order to create the pixmap we will use
+
In order to create the pixmap we will use
[http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Misc.html#v%3AcreatePixmap createPixmap],
+
[http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Misc.html#v%3AcreatePixmap
the interface to
+
createPixmap], the interface to
[http://www.tronche.com/gui/x/xlib/pixmap-and-cursor/XCreatePixmap.htm XCreatePixmap()],
+
[http://www.tronche.com/gui/x/xlib/pixmap-and-cursor/XCreatePixmap.htm
which takes the display connection, the drawable upon which the pixmap
+
XCreatePixmap()], which takes the display connection, the drawable upon which the pixmap is created, the width, the height, and the depth of the screen.
is created, the width, the height, and the depth of the screen.
 
   
We will then use
+
We will then use
[http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Misc.html#v%3AcopyArea copyArea],
+
[http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Misc.html#v%3AcopyArea
the interface to
+
copyArea], the interface to
[http://www.tronche.com/gui/x/xlib/graphics/XCopyArea.html XCopyArea],
+
[http://www.tronche.com/gui/x/xlib/graphics/XCopyArea.html XCopyArea],
 
to copy the pixmap over the window.
 
to copy the pixmap over the window.
   
Line 877: Line 875:
   
 
<haskell>
 
<haskell>
+
copyArea :: Display
copyArea :: Display
+
-> Drawable -> Drawable
-> Drawable -> Drawable
+
-> GC
-> GC
+
-> Position -> Position
-> Position -> Position
+
-> Dimension -> Dimension
-> Dimension -> Dimension
+
-> Position -> Position
-> Position -> Position
+
-> IO ()
-> IO ()
 
 
 
</haskell>
 
</haskell>
   
 
that is to say:
 
that is to say:
 
# the display connection
 
# the display connection
# the origin and the destination drawables (our pixmap and our window respectively)
+
# the origin and the destination drawables (our pixmap and our window
# the x and y coordinates relative to the origin drawable upper left corner
+
respectively)
  +
# the ''x'' and ''y'' coordinates relative to the origin drawable upper left corner
 
# the width and the height of the area the be copied
 
# the width and the height of the area the be copied
# the x and y coordinates relative to the upper left corner of the destination drawable where the copied area must be placed
+
# the ''x'' and ''y'' coordinates relative to the upper left corner of the destination drawable where the copied area must be placed
   
 
And we will eventually free the pixmap with
 
And we will eventually free the pixmap with
[http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Misc.html#v%3AfreePixmap freePixmap],
+
[http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Misc.html#v%3AfreePixmap
the interface to
+
freePixmap], the interface to
[http://www.tronche.com/gui/x/xlib/pixmap-and-cursor/XFreePixmap.html XFreePixmap].
+
[http://www.tronche.com/gui/x/xlib/pixmap-and-cursor/XFreePixmap.html
  +
XFreePixmap].
   
Since our printString function may accept either a window or a pixmap,
+
Since our printString function may accept either a window or a pixmap, they both are drawables, all we need to do is to change drawInWin accordingly:
they both are drawables, all we need to do is to change drawInWin
 
accordingly:
 
   
 
<haskell>
 
<haskell>
Line 904: Line 902:
 
drawInWin :: Display -> Window -> String ->IO ()
 
drawInWin :: Display -> Window -> String ->IO ()
 
drawInWin dpy win str = do
 
drawInWin dpy win str = do
bgcolor <- initColor dpy "green"
+
bgcolor <- initColor dpy "green"
fgcolor <- initColor dpy "blue"
+
fgcolor <- initColor dpy "blue"
gc <- createGC dpy win
+
gc <- createGC dpy win
fontStruc <- loadQueryFont dpy "-misc-fixed-*-*-*-*-10-*-*-*-*-*-*-*"
+
fontStruc <- loadQueryFont dpy "-misc-fixed-*-*-*-*-10-*-*-*-*-*-*-*"
p <- createPixmap dpy win 200 100 (defaultDepthOfScreen (defaultScreenOfDisplay dpy))
+
p <- createPixmap dpy win 200 100 (defaultDepthOfScreen (defaultScreenOfDisplay dpy))
setForeground dpy gc bgcolor
+
setForeground dpy gc bgcolor
fillRectangle dpy p gc 0 0 200 100
+
fillRectangle dpy p gc 0 0 200 100
setForeground dpy gc fgcolor
+
setForeground dpy gc fgcolor
fillRectangle dpy p gc 2 2 196 96
+
fillRectangle dpy p gc 2 2 196 96
printString dpy p gc fontStruc str
+
printString dpy p gc fontStruc str
copyArea dpy p win gc 0 0 200 100 0 0
+
copyArea dpy p win gc 0 0 200 100 0 0
freeGC dpy gc
+
freeGC dpy gc
freeFont dpy fontStruc
+
freeFont dpy fontStruc
freePixmap dpy p
+
freePixmap dpy p
   
 
</haskell>
 
</haskell>
   
Now, all graphic functions take now "p" and not "win". After copyArea
+
Now, all graphic functions take now "p" and not "win". After copyArea everything is freed.
everything is freed.
 
   
 
Our clock:
 
Our clock:
   
 
<haskell>
 
<haskell>
 
 
module Main where
 
module Main where
 
import Data.Bits
 
import Data.Bits
Line 935: Line 932:
   
 
main :: IO ()
 
main :: IO ()
main = do
+
main = do
dpy <- openDisplay ""
+
dpy <- openDisplay ""
let dflt = defaultScreen dpy
+
let dflt = defaultScreen dpy
scr = defaultScreenOfDisplay dpy
+
scr = defaultScreenOfDisplay dpy
rootw <- rootWindow dpy dflt
+
rootw <- rootWindow dpy dflt
win <- mkUnmanagedWindow dpy scr rootw 0 0 200 100
+
win <- mkUnmanagedWindow dpy scr rootw 0 0 200 100
setTextProperty dpy win "Hello World - The Clock" wM_NAME
+
setTextProperty dpy win "Hello World - The Clock" wM_NAME
mapWindow dpy win
+
mapWindow dpy win
updateWin dpy win
+
updateWin dpy win
   
 
updateWin :: Display -> Window -> IO ()
 
updateWin :: Display -> Window -> IO ()
 
updateWin dpy win = do
 
updateWin dpy win = do
drawInWin dpy win =<< date
+
drawInWin dpy win =<< date
sync dpy False
+
sync dpy False
threadDelay (1 * 1000000)
+
threadDelay (1 * 1000000)
updateWin dpy win
+
updateWin dpy win
   
 
date :: IO String
 
date :: IO String
date = do
+
date = do
t <- toCalendarTime =<< getClockTime
+
t <- toCalendarTime =<< getClockTime
return $ calendarTimeToString t
+
return $ calendarTimeToString t
   
 
drawInWin :: Display -> Window -> String ->IO ()
 
drawInWin :: Display -> Window -> String ->IO ()
 
drawInWin dpy win str = do
 
drawInWin dpy win str = do
bgcolor <- initColor dpy "green"
+
bgcolor <- initColor dpy "green"
fgcolor <- initColor dpy "blue"
+
fgcolor <- initColor dpy "blue"
gc <- createGC dpy win
+
gc <- createGC dpy win
fontStruc <- loadQueryFont dpy "-misc-fixed-*-*-*-*-10-*-*-*-*-*-*-*"
+
fontStruc <- loadQueryFont dpy "-misc-fixed-*-*-*-*-10-*-*-*-*-*-*-*"
p <- createPixmap dpy win 200 100 (defaultDepthOfScreen (defaultScreenOfDisplay dpy))
+
p <- createPixmap dpy win 200 100 (defaultDepthOfScreen (defaultScreenOfDisplay dpy))
setForeground dpy gc bgcolor
+
setForeground dpy gc bgcolor
fillRectangle dpy p gc 0 0 200 100
+
fillRectangle dpy p gc 0 0 200 100
setForeground dpy gc fgcolor
+
setForeground dpy gc fgcolor
fillRectangle dpy p gc 2 2 196 96
+
fillRectangle dpy p gc 2 2 196 96
printString dpy p gc fontStruc str
+
printString dpy p gc fontStruc str
copyArea dpy p win gc 0 0 200 100 0 0
+
copyArea dpy p win gc 0 0 200 100 0 0
freeGC dpy gc
+
freeGC dpy gc
freeFont dpy fontStruc
+
freeFont dpy fontStruc
freePixmap dpy p
+
freePixmap dpy p
   
+
printString :: Display
printString :: Display
+
-> Drawable
-> Drawable
+
-> GC
-> GC
+
-> FontStruct
-> FontStruct
+
-> String
-> String
+
-> IO ()
-> IO ()
 
 
printString dpy d gc fontst str =
 
printString dpy d gc fontst str =
do let strLen = textWidth fontst str
+
do let strLen = textWidth fontst str
(_,asc,_,_) = textExtents fontst str
+
(_,asc,_,_) = textExtents fontst str
valign = (100 + fromIntegral asc) `div` 2
+
valign = (100 + fromIntegral asc) `div` 2
remWidth = 200 - strLen
+
remWidth = 200 - strLen
offset = remWidth `div` 2
+
offset = remWidth `div` 2
fgcolor <- initColor dpy "white"
+
fgcolor <- initColor dpy "white"
bgcolor <- initColor dpy "blue"
+
bgcolor <- initColor dpy "blue"
setForeground dpy gc fgcolor
+
setForeground dpy gc fgcolor
setBackground dpy gc bgcolor
+
setBackground dpy gc bgcolor
drawImageString dpy d gc offset valign str
+
drawImageString dpy d gc offset valign str
   
 
mkUnmanagedWindow :: Display
 
mkUnmanagedWindow :: Display
-> Screen
+
-> Screen
-> Window
+
-> Window
-> Position
+
-> Position
-> Position
+
-> Position
-> Dimension
+
-> Dimension
-> Dimension
+
-> Dimension
-> IO Window
+
-> IO Window
 
mkUnmanagedWindow dpy scr rw x y w h = do
 
mkUnmanagedWindow dpy scr rw x y w h = do
let visual = defaultVisualOfScreen scr
+
let visual = defaultVisualOfScreen scr
win <- allocaSetWindowAttributes $
+
win <- allocaSetWindowAttributes $
\attributes -> do
+
\attributes -> do
set_override_redirect attributes True
+
set_override_redirect attributes True
createWindow dpy rw x y w h 0 (defaultDepthOfScreen scr)
+
createWindow dpy rw x y w h 0 (defaultDepthOfScreen scr)
inputOutput visual cWOverrideRedirect attributes
+
inputOutput visual cWOverrideRedirect attributes
return win
+
return win
 
   
 
initColor :: Display -> String -> IO Pixel
 
initColor :: Display -> String -> IO Pixel
 
initColor dpy color = do
 
initColor dpy color = do
let colormap = defaultColormap dpy (defaultScreen dpy)
+
let colormap = defaultColormap dpy (defaultScreen dpy)
(apros,real) <- allocNamedColor dpy colormap color
+
(apros,real) <- allocNamedColor dpy colormap color
return $ color_pixel apros
+
return $ color_pixel apros
 
 
</haskell>
 
</haskell>
 
   
 
==Dealing with events==
 
==Dealing with events==
 
 
Now try this.
 
Now try this.
   
Line 1,025: Line 1,020:
 
<haskell>
 
<haskell>
   
threadDelay (60 * 1000000)
+
threadDelay (60 * 1000000)
   
 
</haskell>
 
</haskell>
   
Run the clock, switch to a console (with Alt+Ctrl+F1) and come back to
+
Run the clock, switch to a console (with Alt+Ctrl+F1) and come back to the X server where the clock is running.
the X server where the clock is running.
 
   
What happened? The window disappeared, and came back after being
+
What happened? The window disappeared, and came back after being redrawn by drawInWin.
redrawn by drawInWin.
 
   
The problem is that our application does not respond to the events the
+
The problem is that our application does not respond to the events the X server is sending to our window. If a window is covered or anyway no more visible on the screen, when the covered area becomes visible again the X server will send to that window an ''Expose'' event, so that the application using that window may redraw it. Since our clock doesn't listen for any event, the window will not be redrawn till a new call to drawInWin is done.
X server is sending to our window. If a window is covered or anyway no
 
more visible on the screen, when the covered area becomes visible
 
again the X server will send to that window an ''Expose'' event, so
 
that the application using that window may redraw it. Since our clock
 
doesn't listen for any event, the window will not be redrawn till a
 
new call to drawInWin is done.
 
   
Citing from
+
Citing from
 
[http://en.wikipedia.org/wiki/X_Window_System_core_protocol#Events Wikipedia]:
 
[http://en.wikipedia.org/wiki/X_Window_System_core_protocol#Events Wikipedia]:
   
<blockquote>
+
<blockquote>"Events are packets sent by the server to a client to communicate that something the client may be interested in has happened. For example, an event is sent when the user presses a key or clicks a mouse button.
Events are packets sent by the server to a client to
 
communicate that something the client may be interested in has
 
happened. For example, an event is sent when the user presses a key or
 
clicks a mouse button.
 
   
Events are not only used for input: for example, events are sent to
+
Events are not only used for input: for example, events are sent to indicate the creation of new subwindows of a given window. Every event is relative to a window. For example, if the user clicks when the pointer is in a window, the event will be relative to that window. The event packet contains the identifier of that window."</blockquote>
indicate the creation of new subwindows of a given window. Every event
 
is relative to a window. For example, if the user clicks when the
 
pointer is in a window, the event will be relative to that window. The
 
event packet contains the identifier of that window.
 
</blockquote>
 
   
The list of events a window will be reacting too is set as the
+
The list of events a window will be reacting too is set as the
[http://www.tronche.com/gui/x/xlib/window/attributes/event-and-do-not-propagate.html event mask]
+
[http://www.tronche.com/gui/x/xlib/window/attributes/event-and-do-not-propagate.html
attribute of that window, and so may be set at creation time, as
+
event mask] attribute of that window, and so may be set at creation time, as we have seen for the background pixel, or with XChangeWindowAttributes, or by using
we have seen for the background pixel, or with
+
[http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Event.html#v%3AselectInput
XChangeWindowAttributes, or by using
+
selectInput], the interface to
[http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Event.html#v%3AselectInput selectInput],
 
the interface to
 
 
[http://www.tronche.com/gui/x/xlib/event-handling/XSelectInput.html XSelectInput].
 
[http://www.tronche.com/gui/x/xlib/event-handling/XSelectInput.html XSelectInput].
   
In any case an event mask,
+
In any case an event mask,
[http://www.tronche.com/gui/x/xlib/window/attributes/event-and-do-not-propagate.html define as]
+
[http://www.tronche.com/gui/x/xlib/window/attributes/event-and-do-not-propagate.html
«the bitwise inclusive OR of zero or more of the valid event mask
+
define as] "the bitwise inclusive OR of zero or more of the valid event mask
bits», must be specified in a way very similar to the attribute mask
+
bits", must be specified in a way very similar to the attribute mask specification.
specification.
 
   
 
This is the type signature of selectInput:
 
This is the type signature of selectInput:
   
 
<haskell>
 
<haskell>
 
 
selectInput :: Display -> Window -> EventMask -> IO ()
 
selectInput :: Display -> Window -> EventMask -> IO ()
 
 
</haskell>
 
</haskell>
   
The possible events to be included in the event must, separated by a
+
The possible events to be included in the event must, separated by a bitwise inclusive OR, are listed [http://www.tronche.com/gui/x/xlib/events/mask.html here] and [http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Types.html#3
bitwise inclusive OR, are listed
+
here].
[http://www.tronche.com/gui/x/xlib/events/mask.html here] and
 
[http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Types.html#3 here].
 
   
For a list of events types refer to the
+
For a list of events types refer to the [http://www.tronche.com/gui/x/xlib/events/types.html Xlib Manual].
[http://www.tronche.com/gui/x/xlib/events/types.html Xlib Manual].
 
   
In order to capture ''Expose'' events, we will need to set the
+
In order to capture ''Expose'' events, we will need to set the [http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Types.html#v%3AexposureMask
[http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Types.html#v%3AexposureMask exposureMask]
+
exposureMask] with something like this, right after the new window has been mapped, in our main function:
with something like this, right after the new window has been mapped,
 
in our main function:
 
   
 
<haskell>
 
<haskell>
+
selectInput dpy win exposureMask
selectInput dpy win exposureMask
 
 
 
</haskell>
 
</haskell>
   
This is all we need to do in order to configure the window in such a
+
This is all we need to do in order to configure the window in such a way that it will receive ''Expose'' events.
way that it will receive ''Expose'' events.
 
   
Our problem is far bigger than that, unfortunately. Our problem,
+
Our problem is far bigger than that, unfortunately. Our problem, indeed, is that we must update (redraw) our window either in the case of an ''Expose'' event is received '''and''' when a given amount of time is elapsed. This second requirement was met by blocking our program execution with threadDelay. But when our program is blocked it cannot receive any event.
indeed, is that we must update (redraw) our window either in the case
 
of an ''Expose'' event is received '''and''' when a given amount of
 
time is elapsed. This second requirement was met by blocking our
 
program execution with threadDelay. But when our program is blocked it
 
cannot receive any event.
 
   
But if we start listening for events and no ''Expose'' event happens,
+
But if we start listening for events and no ''Expose'' event happens, after some time is elapsed we must update our window anyhow.
after some time is elapsed we must update our window anyhow.
 
   
How can we achieve such a result?
+
How can we achieve this?
   
Just to explain our porblem with other words, if we change, in the
+
Just to explain our problem with other words, if we change, in the last example, main and updateWin to listen to events we end up with something like this:
last example, main and updateWin to listen to events we end up with
 
something like this:
 
   
 
<haskell>
 
<haskell>
 
 
main :: IO ()
 
main :: IO ()
main = do
+
main = do
dpy <- openDisplay ""
+
dpy <- openDisplay ""
let dflt = defaultScreen dpy
+
let dflt = defaultScreen dpy
scr = defaultScreenOfDisplay dpy
+
scr = defaultScreenOfDisplay dpy
rootw <- rootWindow dpy dflt
+
rootw <- rootWindow dpy dflt
win <- mkUnmanagedWindow dpy scr rootw 0 0 200 100
+
win <- mkUnmanagedWindow dpy scr rootw 0 0 200 100
setTextProperty dpy win "The Clock" wM_NAME
+
setTextProperty dpy win "The Clock" wM_NAME
mapWindow dpy win
+
mapWindow dpy win
selectInput dpy win (exposureMask .|. buttonPress)
+
selectInput dpy win (exposureMask .|. buttonPress)
updateWin dpy win
+
updateWin dpy win
   
 
updateWin :: Display -> Window -> IO ()
 
updateWin :: Display -> Window -> IO ()
 
updateWin dpy win = do
 
updateWin dpy win = do
drawInWin dpy win =<< date
+
drawInWin dpy win =<< date
sync dpy True
+
sync dpy True
allocaXEvent $ \e ->
+
allocaXEvent $ \e ->
do nextEvent dpy e
+
do nextEvent dpy e
ev <- getEvent e
+
ev <- getEvent e
putStrLn $ eventName ev
+
putStrLn $ eventName ev
updateWin dpy win
+
updateWin dpy win
 
 
</haskell>
 
</haskell>
   
+
In main we added the selectInput call. Note that the event mask includes both ''Expose'' events, and mouse button press events (you may specify different types of events).
In main we added the selectInput call. Note that the event mask
 
includes both ''Expose'' events, and mouse button press events (you
 
may specify different types of events).
 
   
 
The second function, updateWin, required more modifications.
 
The second function, updateWin, required more modifications.
   
Now the sync call takes a True, and not a False any more. This means
+
Now the sync call takes a True, and not a False any more. This means that when flushing the output buffer all events in the event queue will be discarded. This is necessary otherwise we are going to intercept previous events. For instance, if you change the Boolean to False, you will see a
that when flushing the output buffer all events in the event queue
+
''[http://www.tronche.com/gui/x/xlib/events/exposure/graphics-expose-and-no-expose.html
will be discarded. This is necessary otherwise we are going to
+
NoExpose]'' event, that is the result of the application of XCopyArea in
intercept previous events. For instance, if you change the Boolean to
 
False, you will see a
 
''[http://www.tronche.com/gui/x/xlib/events/exposure/graphics-expose-and-no-expose.html NoExpose]''
 
event, that is the result of the application of XCopyArea in
 
 
drawInWin.
 
drawInWin.
   
Please note the use of
+
Please note the use of
[http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Event.html#v%3AallocaXEvent allocaXEvent],
+
[http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Event.html#v%3AallocaXEvent
very similar to the use of allocaSetWindowAttributes, as shown by its
+
allocaXEvent], very similar to the use of allocaSetWindowAttributes, as shown by its type signature:
type signature:
 
   
 
<haskell>
 
<haskell>
 
 
allocaXEvent :: (XEventPtr -> IO a) -> IO a
 
allocaXEvent :: (XEventPtr -> IO a) -> IO a
 
 
</haskell>
 
</haskell>
   
 
Within allocaXEvent we can use the pointer to the XEvent to:
 
Within allocaXEvent we can use the pointer to the XEvent to:
# wait for the next event with [http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Event.html#v%3AnextEvent nextEvent], the interface to [http://www.tronche.com/gui/x/xlib/event-handling/manipulating-event-queue/XNextEvent.html XNextEvent];
+
# wait for the next event with
  +
[http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Event.html#v%3AnextEvent
  +
nextEvent], the interface to
  +
[http://www.tronche.com/gui/x/xlib/event-handling/manipulating-event-queue/XNextEvent.html
  +
XNextEvent];
 
# get the occurred event with getEvent (which requires X11-extras);
 
# get the occurred event with getEvent (which requires X11-extras);
 
# convert the event in a string with eventName (which requires X11-extras);
 
# convert the event in a string with eventName (which requires X11-extras);
Line 1,136: Line 1,126:
 
I've also removed the threadDelay call. Guess why?
 
I've also removed the threadDelay call. Guess why?
   
Just give it a run and you'll find out. If sync discards previous
+
Just give it a run and you'll find out. If sync discards previous events, now nextEvent will block the program execution till an event occurs. If you don't press the mouse button over the window, or force an ''Expose'' event to occur, for instance by switching to a text console and back, the thread will be blocked in the safe foreign call to
events, now nextEvent will block the program execution till an event
+
[http://www.tronche.com/gui/x/xlib/event-handling/manipulating-event-queue/XNextEvent.html
occurs. If you don't press the mouse button over the window, or force
+
XNextEvent], which, "if the event queue is empty, flushes the output buffer and
an ''Expose'' event to occur, for instance by switching to a text
+
blocks until an event is received".
console and back, the thread will be blocked in the safe foreign call
 
to
 
[http://www.tronche.com/gui/x/xlib/event-handling/manipulating-event-queue/XNextEvent.html XNextEvent],
 
which, «if the event queue is empty, flushes the output buffer and
 
blocks until an event is received».
 
   
 
This is the implementation of nextEvent:
 
This is the implementation of nextEvent:
   
 
<haskell>
 
<haskell>
 
 
-- | interface to the X11 library function @XNextEvent()@.
 
-- | interface to the X11 library function @XNextEvent()@.
 
foreign import ccall safe "HsXlib.h XNextEvent"
 
foreign import ccall safe "HsXlib.h XNextEvent"
nextEvent :: Display -> XEventPtr -> IO ()
+
nextEvent :: Display -> XEventPtr -> IO ()
 
 
</haskell>
 
</haskell>
   
Line 1,153: Line 1,142:
   
 
===Events and threads===
 
===Events and threads===
+
One possible solution is to use a second thread to ask the X server to send an ''Expose'' event after some time.
One possible solution is to use a second thread to ask the X server to
 
send an ''Expose'' event after some time.
 
   
 
This is the code:
 
This is the code:
   
 
<haskell>
 
<haskell>
 
 
module Main where
 
module Main where
 
import Data.Bits
 
import Data.Bits
Line 1,168: Line 1,156:
   
 
main :: IO ()
 
main :: IO ()
main = do
+
main = do
dpy <- openDisplay ""
+
dpy <- openDisplay ""
let dflt = defaultScreen dpy
+
let dflt = defaultScreen dpy
scr = defaultScreenOfDisplay dpy
+
scr = defaultScreenOfDisplay dpy
rootw <- rootWindow dpy dflt
+
rootw <- rootWindow dpy dflt
win <- mkUnmanagedWindow dpy scr rootw 0 0 200 100
+
win <- mkUnmanagedWindow dpy scr rootw 0 0 200 100
setTextProperty dpy win "The Clock" wM_NAME
+
setTextProperty dpy win "The Clock" wM_NAME
mapWindow dpy win
+
mapWindow dpy win
selectInput dpy win (exposureMask .|. buttonPress)
+
selectInput dpy win (exposureMask .|. buttonPress)
updateWin dpy win
+
updateWin dpy win
   
 
updateWin :: Display -> Window -> IO ()
 
updateWin :: Display -> Window -> IO ()
 
updateWin dpy win = do
 
updateWin dpy win = do
forkIO $ sendExposeEvent dpy win
+
forkIO $ sendExposeEvent dpy win
drawInWin dpy win =<< date
+
drawInWin dpy win =<< date
sync dpy True
+
sync dpy True
allocaXEvent $ \e ->
+
allocaXEvent $ \e ->
do nextEvent dpy e
+
do nextEvent dpy e
ev <- getEvent e
+
ev <- getEvent e
putStrLn $ eventName ev
+
putStrLn $ eventName ev
updateWin dpy win
+
updateWin dpy win
   
 
sendExposeEvent :: Display -> Window -> IO ()
 
sendExposeEvent :: Display -> Window -> IO ()
sendExposeEvent dpy w =
+
sendExposeEvent dpy w =
do threadDelay (1 * 1000000)
+
do threadDelay (1 * 1000000)
allocaXEvent $ \e -> do
+
allocaXEvent $ \e -> do
setEventType e expose
+
setEventType e expose
sendEvent dpy w False noEventMask e
+
sendEvent dpy w False noEventMask e
sync dpy False
+
sync dpy False
   
 
date :: IO String
 
date :: IO String
date = do
+
date = do
t <- toCalendarTime =<< getClockTime
+
t <- toCalendarTime =<< getClockTime
return $ calendarTimeToString t
+
return $ calendarTimeToString t
   
 
drawInWin :: Display -> Window -> String ->IO ()
 
drawInWin :: Display -> Window -> String ->IO ()
 
drawInWin dpy win str = do
 
drawInWin dpy win str = do
bgcolor <- initColor dpy "green"
+
bgcolor <- initColor dpy "green"
fgcolor <- initColor dpy "blue"
+
fgcolor <- initColor dpy "blue"
gc <- createGC dpy win
+
gc <- createGC dpy win
fontStruc <- loadQueryFont dpy "-misc-fixed-*-*-*-*-10-*-*-*-*-*-*-*"
+
fontStruc <- loadQueryFont dpy "-misc-fixed-*-*-*-*-10-*-*-*-*-*-*-*"
p <- createPixmap dpy win 200 100 (defaultDepthOfScreen (defaultScreenOfDisplay dpy))
+
p <- createPixmap dpy win 200 100 (defaultDepthOfScreen (defaultScreenOfDisplay dpy))
setForeground dpy gc bgcolor
+
setForeground dpy gc bgcolor
fillRectangle dpy p gc 0 0 200 100
+
fillRectangle dpy p gc 0 0 200 100
setForeground dpy gc fgcolor
+
setForeground dpy gc fgcolor
fillRectangle dpy p gc 2 2 196 96
+
fillRectangle dpy p gc 2 2 196 96
printString dpy p gc fontStruc str
+
printString dpy p gc fontStruc str
copyArea dpy p win gc 0 0 200 100 0 0
+
copyArea dpy p win gc 0 0 200 100 0 0
freeGC dpy gc
+
freeGC dpy gc
freeFont dpy fontStruc
+
freeFont dpy fontStruc
freePixmap dpy p
+
freePixmap dpy p
   
+
printString :: Display
printString :: Display
+
-> Drawable
-> Drawable
+
-> GC
-> GC
+
-> FontStruct
-> FontStruct
+
-> String
-> String
+
-> IO ()
-> IO ()
 
 
printString dpy d gc fontst str =
 
printString dpy d gc fontst str =
do let strLen = textWidth fontst str
+
do let strLen = textWidth fontst str
(_,asc,_,_) = textExtents fontst str
+
(_,asc,_,_) = textExtents fontst str
valign = (100 + fromIntegral asc) `div` 2
+
valign = (100 + fromIntegral asc) `div` 2
remWidth = 200 - strLen
+
remWidth = 200 - strLen
offset = remWidth `div` 2
+
offset = remWidth `div` 2
fgcolor <- initColor dpy "white"
+
fgcolor <- initColor dpy "white"
bgcolor <- initColor dpy "blue"
+
bgcolor <- initColor dpy "blue"
setForeground dpy gc fgcolor
+
setForeground dpy gc fgcolor
setBackground dpy gc bgcolor
+
setBackground dpy gc bgcolor
drawImageString dpy d gc offset valign str
+
drawImageString dpy d gc offset valign str
   
 
mkUnmanagedWindow :: Display
 
mkUnmanagedWindow :: Display
-> Screen
+
-> Screen
-> Window
+
-> Window
-> Position
+
-> Position
-> Position
+
-> Position
-> Dimension
+
-> Dimension
-> Dimension
+
-> Dimension
-> IO Window
+
-> IO Window
 
mkUnmanagedWindow dpy scr rw x y w h = do
 
mkUnmanagedWindow dpy scr rw x y w h = do
let visual = defaultVisualOfScreen scr
+
let visual = defaultVisualOfScreen scr
win <- allocaSetWindowAttributes $
+
win <- allocaSetWindowAttributes $
\attributes -> do
+
\attributes -> do
set_override_redirect attributes True
+
set_override_redirect attributes True
createWindow dpy rw x y w h 0 (defaultDepthOfScreen scr)
+
createWindow dpy rw x y w h 0 (defaultDepthOfScreen scr)
inputOutput visual cWOverrideRedirect attributes
+
inputOutput visual cWOverrideRedirect attributes
return win
+
return win
 
   
 
initColor :: Display -> String -> IO Pixel
 
initColor :: Display -> String -> IO Pixel
 
initColor dpy color = do
 
initColor dpy color = do
let colormap = defaultColormap dpy (defaultScreen dpy)
+
let colormap = defaultColormap dpy (defaultScreen dpy)
(apros,real) <- allocNamedColor dpy colormap color
+
(apros,real) <- allocNamedColor dpy colormap color
return $ color_pixel apros
+
return $ color_pixel apros
 
 
 
</haskell>
 
</haskell>
   
This is going to work only if compiled with the ghc flag ''-threaded''
+
This is going to work only if compiled with the ghc flag ''-threaded'', otherwise it will not work.
otherwise it will not work.
 
   
A clear explanation of why can be found
+
A clear explanation of why can be found
[http://haskell.org/ghc/docs/latest/html/libraries/base/Control-Concurrent.html#10 here].
+
[http://haskell.org/ghc/docs/latest/html/libraries/base/Control-Concurrent.html#10
  +
here].
   
 
===A new nextEvent with asynchronous exceptions===
 
===A new nextEvent with asynchronous exceptions===
 
 
This is a second solution and was proposed by Spencer Janssen.
 
This is a second solution and was proposed by Spencer Janssen.
   
It uses a version of nextEvent that will not block in a foreign call.
+
It uses a version of nextEvent that will not block in a foreign call. An
An
+
[http://haskell.org/ghc/docs/latest/html/libraries/base/Control-Exception.html#14
[http://haskell.org/ghc/docs/latest/html/libraries/base/Control-Exception.html#14 asynchronous exception]
+
asynchronous exception] will be used to interrupt threadWaitRead.
will be used to interrupt threadWaitRead.
 
   
This is the code.
+
This is the code:
   
 
<haskell>
 
<haskell>
 
 
import Prelude hiding (catch)
 
import Prelude hiding (catch)
 
import Data.Bits
 
import Data.Bits
Line 1,288: Line 1,274:
 
import Control.Exception
 
import Control.Exception
 
import System.Posix.Types (Fd(..))
 
import System.Posix.Types (Fd(..))
 
   
 
main :: IO ()
 
main :: IO ()
main = do
+
main = do
dpy <- openDisplay ""
+
dpy <- openDisplay ""
let dflt = defaultScreen dpy
+
let dflt = defaultScreen dpy
scr = defaultScreenOfDisplay dpy
+
scr = defaultScreenOfDisplay dpy
rootw <- rootWindow dpy dflt
+
rootw <- rootWindow dpy dflt
win <- mkUnmanagedWindow dpy scr rootw 0 0 200 100
+
win <- mkUnmanagedWindow dpy scr rootw 0 0 200 100
setTextProperty dpy win "The Clock" wM_NAME
+
setTextProperty dpy win "The Clock" wM_NAME
mapWindow dpy win
+
mapWindow dpy win
selectInput dpy win (exposureMask .|. buttonPress)
+
selectInput dpy win (exposureMask .|. buttonPress)
updateWin dpy win
+
updateWin dpy win
   
 
-- | A version of nextEvent that does not block in foreign calls.
 
-- | A version of nextEvent that does not block in foreign calls.
 
nextEvent' :: Display -> XEventPtr -> IO ()
 
nextEvent' :: Display -> XEventPtr -> IO ()
 
nextEvent' d p = do
 
nextEvent' d p = do
pend <- pending d
+
pend <- pending d
if pend /= 0
+
if pend /= 0
then nextEvent d p
+
then nextEvent d p
else do
+
else do
threadWaitRead (Fd fd)
+
threadWaitRead (Fd fd)
nextEvent' d p
+
nextEvent' d p
 
where
 
where
fd = connectionNumber d
+
fd = connectionNumber d
   
 
-- | The event loop
 
-- | The event loop
 
updateWin :: Display -> Window -> IO ()
 
updateWin :: Display -> Window -> IO ()
 
updateWin dpy win = do
 
updateWin dpy win = do
t <- forkIO (block go)
+
t <- forkIO (block go)
timer t
+
timer t
 
where
 
where
-- interrupt the drawing thread every so often
+
-- interrupt the drawing thread every so often
timer t = do
+
timer t = do
threadDelay (1 * 1000000)
+
threadDelay (1 * 1000000)
throwTo t (ErrorCall "done")
+
throwTo t (ErrorCall "done")
timer t
+
timer t
-- Continuously wait for a timer interrupt or an expose event
+
-- Continuously wait for a timer interrupt or an expose event
go = do
+
go = do
drawInWin dpy win =<< date
+
drawInWin dpy win =<< date
catch (unblock $ allocaXEvent $ nextEvent' dpy) (const $ return ())
+
catch (unblock $ allocaXEvent $ nextEvent' dpy) (const $ return ())
go
+
go
   
 
sendExposeEvent :: Display -> Window -> IO ()
 
sendExposeEvent :: Display -> Window -> IO ()
sendExposeEvent dpy w =
+
sendExposeEvent dpy w =
do threadDelay (1 * 1000000)
+
do threadDelay (1 * 1000000)
allocaXEvent $ \e -> do
+
allocaXEvent $ \e -> do
setEventType e expose
+
setEventType e expose
sendEvent dpy w False noEventMask e
+
sendEvent dpy w False noEventMask e
sync dpy False
+
sync dpy False
   
 
date :: IO String
 
date :: IO String
date = do
+
date = do
t <- toCalendarTime =<< getClockTime
+
t <- toCalendarTime =<< getClockTime
return $ calendarTimeToString t
+
return $ calendarTimeToString t
   
 
drawInWin :: Display -> Window -> String ->IO ()
 
drawInWin :: Display -> Window -> String ->IO ()
 
drawInWin dpy win str = do
 
drawInWin dpy win str = do
bgcolor <- initColor dpy "green"
+
bgcolor <- initColor dpy "green"
fgcolor <- initColor dpy "blue"
+
fgcolor <- initColor dpy "blue"
gc <- createGC dpy win
+
gc <- createGC dpy win
fontStruc <- loadQueryFont dpy "-misc-fixed-*-*-*-*-10-*-*-*-*-*-*-*"
+
fontStruc <- loadQueryFont dpy "-misc-fixed-*-*-*-*-10-*-*-*-*-*-*-*"
p <- createPixmap dpy win 200 100 (defaultDepthOfScreen (defaultScreenOfDisplay dpy))
+
p <- createPixmap dpy win 200 100 (defaultDepthOfScreen (defaultScreenOfDisplay dpy))
setForeground dpy gc bgcolor
+
setForeground dpy gc bgcolor
fillRectangle dpy p gc 0 0 200 100
+
fillRectangle dpy p gc 0 0 200 100
setForeground dpy gc fgcolor
+
setForeground dpy gc fgcolor
fillRectangle dpy p gc 2 2 196 96
+
fillRectangle dpy p gc 2 2 196 96
printString dpy p gc fontStruc str
+
printString dpy p gc fontStruc str
copyArea dpy p win gc 0 0 200 100 0 0
+
copyArea dpy p win gc 0 0 200 100 0 0
freeGC dpy gc
+
freeGC dpy gc
freeFont dpy fontStruc
+
freeFont dpy fontStruc
freePixmap dpy p
+
freePixmap dpy p
   
+
printString :: Display
printString :: Display
+
-> Drawable
-> Drawable
+
-> GC
-> GC
+
-> FontStruct
-> FontStruct
+
-> String
-> String
+
-> IO ()
-> IO ()
 
 
printString dpy d gc fontst str =
 
printString dpy d gc fontst str =
do let strLen = textWidth fontst str
+
do let strLen = textWidth fontst str
(_,asc,_,_) = textExtents fontst str
+
(_,asc,_,_) = textExtents fontst str
valign = (100 + fromIntegral asc) `div` 2
+
valign = (100 + fromIntegral asc) `div` 2
remWidth = 200 - strLen
+
remWidth = 200 - strLen
offset = remWidth `div` 2
+
offset = remWidth `div` 2
fgcolor <- initColor dpy "white"
+
fgcolor <- initColor dpy "white"
bgcolor <- initColor dpy "blue"
+
bgcolor <- initColor dpy "blue"
setForeground dpy gc fgcolor
+
setForeground dpy gc fgcolor
setBackground dpy gc bgcolor
+
setBackground dpy gc bgcolor
drawImageString dpy d gc offset valign str
+
drawImageString dpy d gc offset valign str
   
 
mkUnmanagedWindow :: Display
 
mkUnmanagedWindow :: Display
-> Screen
+
-> Screen
-> Window
+
-> Window
-> Position
+
-> Position
-> Position
+
-> Position
-> Dimension
+
-> Dimension
-> Dimension
+
-> Dimension
-> IO Window
+
-> IO Window
 
mkUnmanagedWindow dpy scr rw x y w h = do
 
mkUnmanagedWindow dpy scr rw x y w h = do
let visual = defaultVisualOfScreen scr
+
let visual = defaultVisualOfScreen scr
win <- allocaSetWindowAttributes $
+
win <- allocaSetWindowAttributes $
\attributes -> do
+
\attributes -> do
set_override_redirect attributes True
+
set_override_redirect attributes True
createWindow dpy rw x y w h 0 (defaultDepthOfScreen scr)
+
createWindow dpy rw x y w h 0 (defaultDepthOfScreen scr)
inputOutput visual cWOverrideRedirect attributes
+
inputOutput visual cWOverrideRedirect attributes
return win
+
return win
 
   
 
initColor :: Display -> String -> IO Pixel
 
initColor :: Display -> String -> IO Pixel
 
initColor dpy color = do
 
initColor dpy color = do
let colormap = defaultColormap dpy (defaultScreen dpy)
+
let colormap = defaultColormap dpy (defaultScreen dpy)
(apros,real) <- allocNamedColor dpy colormap color
+
(apros,real) <- allocNamedColor dpy colormap color
return $ color_pixel apros
+
return $ color_pixel apros
 
 
</haskell>
 
</haskell>
   

Revision as of 16:10, 4 August 2007

This tutorial will show you how to write a simple X application using the low level C library, Xlib. The goal is to write a simple text-based clock displaying the system time, running on top of every other application - like a status bar would.

While the application is fairly simple, it still requires us to know quite a few details about X and Xlib to write a proper X application.

Contents

1 Target audience

This tutorial is dedicated to the intermediate Haskell coder. While I will try to write the simplest code I can (probably it will even look the dumbest, but that's me), I'm not going into much details about the Haskell part.

2 Goals

What are we going to learn:

  • how to create a window and set, or change, its attributes;
  • how to draw in that window, specifically some text, with some properties, like

fonts or colors;

  • how to properly update the window;
  • how to handle events, like a mouse button press.

3 Background reading

These are some links that can be used as reference:

manual, and you should look up here every function that we are going to use in this tutorial.

protocol (Wikipedia)]

4 Prerequisites

In order to compile the following code examples you need at least:

Haskell binding to the X11 graphics library.

X11-extras]: will be required in some examples. This library provides missing bindings to the X11 graphics library and is being actively developed by Spencer Janssen at the time of this writing; it is notably being used in Xmonad. Some functions needed in this tutorial can be found only in the darcs repository of X11-extras: [http://darcs.haskell.org/~sjanssen/X11-extras http://darcs.haskell.org/~sjanssen/X11-extras]. Read carefully the README before installing.

5 Hello world!

Let's start with the usual simple "Hello World":

module Main where
import Graphics.X11.Xlib
import System.Exit (exitWith, ExitCode(..))
import Control.Concurrent (threadDelay)
 
main :: IO ()
main =
 do dpy <- openDisplay ""
 let dflt = defaultScreen dpy
 border = blackPixel dpy dflt
 background = whitePixel dpy dflt
 rootw <- rootWindow dpy dflt
 win <- createSimpleWindow dpy rootw 0 0 100 100 1 border background
 setTextProperty dpy win "Hello World" wM_NAME
 mapWindow dpy win
 sync dpy False
 threadDelay (10 * 1000000)
 exitWith ExitSuccess

The first function, [http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Display.html#v%3AopenDisplay openDisplay], is the interface to the Xlib function XOpenDisplay(), and opens a connection to the X sever that controls a display. The connection is returned and bound to dpy. By applying [http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Display.html#v%3AdefaultScreen defaultScreen], the interface to [http://www.tronche.com/gui/x/xlib/display/display-macros.html#DefaultScreen XDefaultScreen], we get the default screen, that is required in many of the following functions. With [http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Display.html#v%3ArootWindow rootWindow], the interface to [http://www.tronche.com/gui/x/xlib/display/display-macros.html#RootWindow XRootWindow()], we get the root window. We need it in order to set the parent window in the most important function of the above code: [http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Window.html#v%3AcreateSimpleWindow createSimpleWindow], the interface to XCreateSimpleWindow.

This function takes as arguments: the display, the parent window of the window to be created, the x position, the y position, the width, the height, the border width, the border pixel, and the background pixel.

The x and y positions are relative to the upper left corner of the parent window's inside borders.

In order to retrieve the values of the black and white pixels for that specific screen, we use two specific functions: [http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Display.html#v%3AblackPixel blackPixel], the interface to the X11 library function [http://www.tronche.com/gui/x/xlib/display/display-macros.html#BlackPixel BlackPixel], and [http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Display.html#v%3AwhitePixel whitePixel], the interface to the X11 library function [http://www.tronche.com/gui/x/xlib/display/display-macros.html#WhitePixel WhitePixel]

The function createSimpleWindow will return the window ID and, with this ID, we can start manipulating our newly created window, as we do, in the above code, with the function [http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Misc.html#v%3AsetTextProperty setTextProperty], interface to the X11 library function [http://www.tronche.com/gui/x/xlib/ICC/client-to-window-manager/XSetTextProperty.html XSetTextProperty()].

This function is needed by our code to set the window's name so your window manager will display on some decoration attached to the window (other window managers will not display anything, for instance a tiling WM like Xmonad)

To set the window's name we need to manipulate the XTextProperty structure.

Properties, such as the XTextProperty, have a string name and a numerical identifier called an atom. An atom is an ID that uniquely identifies a particular property. Property name strings are typically all upper case - with the first letter in low case when translated into Haskell - with words separated by underscores. In our example, we set the WM_NAME property to "Hello World".

Creating and manipulating a window is just the first step to have a new window displayed. In order for the window to become visible we must map it with [http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Window.html#v%3AmapWindow mapWindow], the interface to the X11 library function XMapWindow(). This will make the window visible.

Xlib will not send requests and calls to the X server immediately, but will buffer them and send the full buffer when some given conditions are met.

One way to force the flushing of the output buffer is to call [http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Event.html#v%3Async sync], the interface to the X11 library function XSync(), which takes 2 arguments: the connection (dpy) and a Boolean value that indicates whether XSync() must discard all events on the event queue.

After that, the X server will eventually display our window.

The rest of the above example does nothing else but block execution for 10 seconds (to let you stare at your newly created window) and then exits.

6 Window attributes

Even though in our "Hello World" example we set the window's dimension, we have no assurance that the window manager will respect our decision.

Xmonad, for instance, will just create a window with the dimensions needed to fill its tiled screen, no matter what you set in createSimpleWindow.

But we decided to write a small clock that will behave as a statusbar, that is to say, we want to create a window that will specifically not be managed by the window manager.

In order to achieve this result we need to start dealing with window's attributes.

There are two ways of dealing with window's attributes: the first is to set them at window's creation time, but in that case createSimpleWindow is not powerful enough.

The second way is to change window's attributes after the window's has been created. This second approach is not implemented X11 but has been implemented in the darcs version of [http://hackage.haskell.org/cgi-bin/hackage-scripts/package/X11-extras X11-extras].

6.1 Setting window's attribute at creation time

In order to set window's attributes at creation time, the window must be created with [http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Window.html#v%3AcreateWindow createWindow], the interface to the X11 library function XCreateWindow().

The type signature of this function is quite long:

createWindow :: Display -> Window
 -> Position -> Position
 -> Dimension -> Dimension
 -> CInt
 -> CInt
 -> WindowClass
 -> Visual
 -> AttributeMask
 -> Ptr SetWindowAttributes
 -> IO Window

That is to say:

  • the connection and the parent window
  • the x and y position (origins in the upper left corner of the inside border of

the parent window)

  • width and height
  • border width
  • depth of screen
  • the window's class
  • the visual
  • the attribute mask
  • and the pointer to the

[http://www.tronche.com/gui/x/xlib/window/attributes/#XSetWindowAttributes XSetWindowAttributes] foreign C structure.

This last one gives you an idea of the type of operation we must do in order to create a window (createSimpleWindow is just a specialization of this more complicated createWindow, with some arguments filled in with defaults): we need a function to allocate some memory for the creation of the foreign C structure, and then manipulate this foreign structure from within this function.

The needed function is allocaSetWindowAttributes, whose type indeed is:

allocaSetWindowAttributes :: (Ptr SetWindowAttributes -> IO a) -> IO a

allocaSetWindowAttributes will take a function which takes the pointer to the foreign structure as its argument. This function will perform an IO action that is the action returned by allocaSetWindowAttributes.

In our case allocaSetWindowAttributes will take a function that will use createWindow to return the new window.

So, we will need to use createWindow inside allocaSetWindowAttributes. We will soon see how. But first let's analyze the other arguments of createWindow.

The display, the parent window, the coordinates and dimensions are the same as with createSimpleWindow. But now we must specify the depth of the screen, the window's class, the visual and the attribute mask. We also need to manipulate the XSetWindowAttribute after its creation by allocaSetWindowAttributes, before calling createWindow.

The depth is the number of bits available for each pixel to represent colors while the visual is way pixel values are translated to produce colors on the monitor.

We are going to use defaultDepthOfScreen, interface to the X11 library function XDefaultDepthOfScreen(), in order to retrieve the default screen depth.

For the visual we are going to use defaultVisualOfScreen, interface to the X11 library function DefaultVisualOfScreen.

The [http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Types.html#t%3AWindowClass WindowClass] can either be copyFromParent, inputOutput, or inputOnly. In the first case the class is copied from the class of the parent window. An inputOnly window can only be used for receiving input events. In our code we are going to use inputOutput windows, windows that can receive input events and that can also be used to display some output.

The attributeMask "specifies which window attributes are defined in the attributes argument. This mask is the bitwise inclusive OR of the valid attribute mask bits. If value mask is zero, the attributes are ignored and are not referenced." (see http://www.tronche.com/gui/x/xlib/window/XCreateWindow.html).

In other words, in order to set more then one attribute, you need to pass a value mask such as:

attrmask = cWOverrideRedirect .|. cWBorderPixel .|. cWBackPixel .|. etc ...

and set each of this attributes within allocaSetWindowAttributes with specific attributes setting functions.

Among these functions the one we need: set_override_redirect, whose type is:

set_override_redirect :: Ptr SetWindowAttributes -> Bool -> IO ()

This function takes the pointer to the XSetWindowAttributes structure and the flag to be set (True or False).

For the list of available attributes see [http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Types.html#t%3AAttributeMask the AttributeMask type definition].

For their meaning see [http://www.tronche.com/gui/x/xlib/window/attributes/#XSetWindowAttributes the XSetWindowAttributes structure reference].

Now, our goal was to create a window that the window manager is going to ignore, and in order to do that all we need to set the attribute [http://www.tronche.com/gui/x/xlib/window/attributes/override-redirect.html CWOverrideRedirect] to True. And now we know how to do it.

Okay, it's time to introduce our function to create new windows with the CWOverrideRedirect set to True

mkUnmanagedWindow :: Display
 -> Screen
 -> Window
 -> Position
 -> Position
 -> Dimension
 -> Dimension
 -> IO Window
mkUnmanagedWindow dpy scr rw x y w h = do
 let visual = defaultVisualOfScreen scr
 attrmask = cWOverrideRedirect
 win <- allocaSetWindowAttributes $
 \attributes -> do
 set_override_redirect attributes True
 createWindow dpy rw x y w h 0 (defaultDepthOfScreen scr)
 inputOutput visual attrmask attributes
 return win

Like simpleCreateWindow, our function is a wrapper around createWindow, but this time we are manually setting the CWOverrideRedirect flag.

As you see our function, unlike createSimpleWindow, does not have, among its arguments, the background and the border pixels. This colors can be set, for windows created with createWindow, using the attribute mask, and setting CWBackPixel and CWBorderPixel with the needed functions: set_background_pixel and [http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Misc.html#v%3Aset_border_pixel set_border_pixel].

By the way, setting the border color with this version of mkUnmanagedWindow is actually useless since the border width is set to zero. In the next example we will set it to 1.

Our function needs also the screen now, since we have to retrieve the default depth and visual.

We can now rewrite our initial code using the new function now.

module Main where
import Data.Bits
import Graphics.X11.Xlib
import System.Exit (exitWith, ExitCode(..))
import Control.Concurrent (threadDelay)
 
main :: IO ()
main =
 do dpy <- openDisplay ""
 let dflt = defaultScreen dpy
 scr = defaultScreenOfDisplay dpy
 rootw <- rootWindow dpy dflt
 win <- mkUnmanagedWindow dpy scr rootw 0 0 100 100
 setTextProperty dpy win "Hello World" wM_NAME
 mapWindow dpy win
 sync dpy False
 threadDelay (10 * 1000000)
 exitWith ExitSuccess
 
mkUnmanagedWindow :: Display
 -> Screen
 -> Window
 -> Position
 -> Position
 -> Dimension
 -> Dimension
 -> IO Window
mkUnmanagedWindow dpy scr rw x y w h = do
 let visual = defaultVisualOfScreen scr
 attrmask = cWOverrideRedirect .|. cWBorderPixel .|. cWBackPixel
 win <- allocaSetWindowAttributes $
 \attributes -> do
 set_override_redirect attributes True
 set_background_pixel attributes $ whitePixel dpy (defaultScreen dpy)
 set_border_pixel attributes $ blackPixel dpy (defaultScreen dpy)
 createWindow dpy rw x y w h 1 (defaultDepthOfScreen scr)
 inputOutput visual attrmask attributes
 return win

Okay, let's give it a try. Did you see? Now the window will be placed in the specified x and y position, with the given dimensions. No window manager decorations, and so, no name displayed.

6.2 Changing an existing window's attributes

This task requires [http://www.tronche.com/gui/x/xlib/window/XChangeWindowAttributes.html XChangeWindowAttrbutes()], implemented only in the darcs version of [http://hackage.haskell.org/cgi-bin/hackage-scripts/package/X11-extras X11-extras].

In order to change a window's attributes we just need the window ID in that specific X server, after that we need to unmap the window first, and then change its attributes with changeWindowAttributes, the interface to XChangeWindowAttrbutes() implemented by the darcs version of X11-extras.

Here's the code:

module Main where
 
import Graphics.X11.Xlib
import Graphics.X11.Xlib.Extras
import System.Environment
 
usage :: String -> String
usage n = "Usage: " ++ n ++ " manage/unmanage windowID"
 
main :: IO ()
main = do
 args <- getArgs
 pn <- getProgName
 let (win,ac) = case args of
 [] -> error $ usage pn
 w -> case (w !!0) of
 "manage" -> (window, False)
 "unmanage" -> (window, True)
 _ -> error $ usage pn
 where window = case (w !! 1) of
 [] -> error $ usage pn
 w -> read w :: Window
 dpy <- openDisplay ""
 unmapWindow dpy win
 sync dpy False
 allocaSetWindowAttributes $
 \attributes -> do
 set_override_redirect attributes ac
 changeWindowAttributes dpy win cWOverrideRedirect attributes
 mapWindow dpy win
 sync dpy False

Save it as Unmanage.hs and compile with:

ghc --make Unmanage.hs -o unmanage

To use it you need to retrieve the window ID with the stand alone utility

xwininfo

Then you run the above code with:

unmanage unmanage/manage windowID

to set override_redirect to True or False.

Obviously the important part of the code is this:

 dpy <- openDisplay ""
 unmapWindow dpy win
 sync dpy False
 allocaSetWindowAttributes $
 \attributes -> do
 set_override_redirect attributes ac
 changeWindowAttributes dpy win cWOverrideRedirect attributes
 mapWindow dpy win
 sync dpy False

where we:

  1. connect to the X server
  2. unmap the window
  3. flush the output buffer to have the X server actually unmap the window
  4. change the attributes with the same procedure we used to set them when creating

the window

  1. map the window again
  2. flush the output buffer to see the change take effect.

You can modify this program to change other window's attributes.

7 Colors and color Ddepth

So far we have set the window background color as a window attribute. This is not the most convenient way to set the window background color: if we need to change it, we must change the window's attribute, and we have seen that this task requires unmapping the window, flushing the output with changeWindowAttributes within changeWindowAttributes, remapping the window and reflushing the output buffer. Moreover we can do that only with the darcs version of X11-extras...

In the following sections we are going to adopt a more efficient way of setting the window's background color: we will start drawing into the window. But first we must familiarize with colors and the way the X server deals with them.

So far we have set the colors by using some functions to retrieve their pixel values: blackPixel and whitePixel. These functions take the display and the default screen and return respectively the pixel values for the black and the white colors in that screen.

A color is represented by a 32-bit unsigned integer, called a pixel value. The elements affecting the pixel representation of a color are:

  1. the color depth;
  2. the colormap, which is a table containing red, green, and blue intensity values;
  3. the visual type.

All these elements are specific to a given piece of hardware, and so our X application must detect them in order to set colors appropriately for that given hardware.

The approach we are going to use to accomplish this task is this: we are going to use named colors, or colors represented by RGB triple, such as "red", "yellow", or "#FFFFFF", etc; and we are going to translate these colors into the pixel values appropriate for the screen we are operating on.

In order to achieve our goal we are going to use the function [http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Color.html#v%3AallocNamedColor allocNamedColor] which is the interface to the X11 library function [http://www.tronche.com/gui/x/xlib/color/XAllocNamedColor.html XAllocNamedColor()].

The type signature of allocNamedColor is:

allocNamedColor :: Display -> Colormap -> String -> IO (Color, Color)

That is to say, given a display connection, a color map and a string - our color representation -, this function will return a tuple with the closest RGB values provided by the hardware and the exact RGB values, both encoded in a [http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib.html#v%3AColor Haskell Color data type]. We will use the first approximated value.

The Color data type has a field name we will use to retrieve the needed pixel value: [http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib.html#v%3Acolor_pixel color_pixel].

We can now write this helper function:

initColor :: Display -> String -> IO Pixel
initColor dpy color = do
 let colormap = defaultColormap dpy (defaultScreen dpy)
 (approx,real) <- allocNamedColor dpy colormap color
 return $ color_pixel approx

To retrieve the colormap of the screen we used [http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Display.html#v%3AdefaultColormap defaultColormap], the interface to the X11 library function [http://www.tronche.com/gui/x/xlib/display/display-macros.html#DefaultColormap XDefaultColormap()], which requires the display and the screen, and returns the colormap of that screen.

We can now rewrite our example using this new approach:

module Main where
import Data.Bits
import Graphics.X11.Xlib
import System.Exit (exitWith, ExitCode(..))
import Control.Concurrent (threadDelay)
 
main :: IO ()
main =
 do dpy <- openDisplay ""
 let dflt = defaultScreen dpy
 scr = defaultScreenOfDisplay dpy
 rootw <- rootWindow dpy dflt
 win <- mkUnmanagedWindow dpy scr rootw 0 0 100 100
 setTextProperty dpy win "Hello World" wM_NAME
 mapWindow dpy win
 sync dpy False
 threadDelay (10 * 1000000)
 exitWith ExitSuccess
 
mkUnmanagedWindow :: Display
 -> Screen
 -> Window
 -> Position
 -> Position
 -> Dimension
 -> Dimension
 -> IO Window
mkUnmanagedWindow dpy scr rw x y w h = do
 let visual = defaultVisualOfScreen scr
 attrmask = cWOverrideRedirect .|. cWBorderPixel .|. cWBackPixel
 background_color <- initColor dpy "red"
 border_color <- initColor dpy "black"
 win <- allocaSetWindowAttributes $
 \attributes -> do
 set_override_redirect attributes True
 set_background_pixel attributes background_color
 set_border_pixel attributes border_color
 createWindow dpy rw x y w h 1 (defaultDepthOfScreen scr)
 inputOutput visual attrmask attributes
 return win
 
initColor :: Display -> String -> IO Pixel
initColor dpy color = do
 let colormap = defaultColormap dpy (defaultScreen dpy)
 (apros,real) <- allocNamedColor dpy colormap color
 return $ color_pixel apros

Just give it a try. Now you can also experiment with different colors. This approach will assure that our application will work no matter the color depth of the screen we are working on.

8 Drawing in windows

The X server provides two objects that can be used to draw something to: windows and pixmaps.

In this section we will start drawing into windows.

We have seen that changing the background color of a window is a troublesome operation, since the window must be unmapped and remapped, memory for a foreign structure allocated, and so on.

Instead, we can use some graphic operations to draw a rectangle over the window. We will latter manipulate the foreground, visible, color of this rectangle, that will become the new background of our window.

We can also use multiple rectangles with different dimension to decorate our window with a border, for instance. Later on we will print some text over these rectangles.

8.1 Drawing rectangles in a window

Citing from [http://en.wikipedia.org/wiki/X_Window_System_core_protocol#Graphic_contexts_and_fonts Wikipedia]:

The client can request a number of graphic operations, such clearing an area, copying an area into another, drawing points, lines, rectangles, and text. Beside clearing, all operations are possible on all drawables, both windows and pixmaps. Most requests for graphic operations include a graphic context, which is a structure that contains the parameters of the graphic operations. A graphic context includes the foreground color, the background color, the font of text, and other graphic parameters. When requesting a graphic operation, the client includes a graphic context.

In other words, as for setting window's attribute, we must use a foreign C structure to set parameters for graphic operations, and then we will feed this structure to the functions that will perform these graphic operations.

There is one difference: instead of operating within a function that allocates memory and creates a pointer to the foreign structure, now we have to explicitly create the Graphic Context, and free it after having used it, with [http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Context.html#v%3AcreateGC createGC], the interface to [http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Context.html#v%3AcreateGC XCreateGC], and [http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Context.html#v%3AfreeGC freeGC], the interface to XFreeGC.

Be careful: if you create a graphic context without freeing it after use, you are going to end up with a sizeable memory leak!

The specific graphic functions we are going to need for drawing a rectangle into our window are:

setForegrond the interface to [http://www.tronche.com/gui/x/xlib/GC/convenience-functions/XSetForeground.html XSetForegroung]

[http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Context.html#v%3AsetBackground setBackground] the interface to [http://www.tronche.com/gui/x/xlib/GC/convenience-functions/XSetBackground.html XSetBackground]

[http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Misc.html#v%3AfillRectangle fillRectangle] the interface to [http://www.tronche.com/gui/x/xlib/graphics/filling-areas/XFillRectangle.html XFillRectangle]

The first two functions are needed to set the parameters in the Graphic Context. The third one will use this GC for filling a rectangle on the specified window. Just have a look to their type signatures:

setForeground :: Display -> GC -> Pixel -> IO ()
setBackground :: Display -> GC -> Pixel -> IO ()
fillRectangle :: Display -> Drawable -> GC -> Position -> Position -> Dimension ->
Dimension -> IO ()

Okay, this is the function that we will be using for drawing into a window:

drawInWin :: Display -> Window -> IO ()
drawInWin dpy win = do
 fgcolor <- initColor dpy "blue"
 gc <- createGC dpy win
 setForeground dpy gc fgcolor
 fillRectangle dpy win gc 0 0 100 100
 freeGC dpy gc

This will just fill our window with a rectangle at (0, 0) (x, y) coordinates (relatives to the window's internal border), with the same dimensions of our window.

Obviously we can play a bit with rectangles. This version, for instance, will draw 2 rectangles to simulate a blue rectangle with a green border, two pixels width:

drawInWin :: Display -> Window -> IO ()
drawInWin dpy win = do
 bgcolor <- initColor dpy "green"
 fgcolor <- initColor dpy "blue"
 gc <- createGC dpy win
 setForeground dpy gc bgcolor
 fillRectangle dpy win gc 0 0 100 100
 setForeground dpy gc fgcolor
 fillRectangle dpy win gc 2 2 96 96
 freeGC dpy gc

You can use this function on a mapped window. This is our original example, rewritten with this approach:

module Main where
import Data.Bits
import Graphics.X11.Xlib
import System.Exit (exitWith, ExitCode(..))
import Control.Concurrent (threadDelay)
 
main :: IO ()
main =
 do dpy <- openDisplay ""
 let dflt = defaultScreen dpy
 scr = defaultScreenOfDisplay dpy
 rootw <- rootWindow dpy dflt
 win <- mkUnmanagedWindow dpy scr rootw 0 0 100 100
 setTextProperty dpy win "Hello World" wM_NAME
 mapWindow dpy win
 drawInWin dpy win
 sync dpy False
 threadDelay (10 * 1000000)
 exitWith ExitSuccess
 
drawInWin :: Display -> Window -> IO ()
drawInWin dpy win = do
 bgcolor <- initColor dpy "green"
 fgcolor <- initColor dpy "blue"
 gc <- createGC dpy win
 setForeground dpy gc bgcolor
 fillRectangle dpy win gc 0 0 100 100
 setForeground dpy gc fgcolor
 fillRectangle dpy win gc 2 2 96 96
 freeGC dpy gc
 
mkUnmanagedWindow :: Display
 -> Screen
 -> Window
 -> Position
 -> Position
 -> Dimension
 -> Dimension
 -> IO Window
mkUnmanagedWindow dpy scr rw x y w h = do
 let visual = defaultVisualOfScreen scr
 win <- allocaSetWindowAttributes $
 \attributes -> do
 set_override_redirect attributes True
 createWindow dpy rw x y w h 0 (defaultDepthOfScreen scr)
 inputOutput visual cWOverrideRedirect attributes
 return win
 
initColor :: Display -> String -> IO Pixel
initColor dpy color = do

As you see, now mkUnmanagedWindow sets a null border width and does not set any background color. Everything is easily done with rectangles.

8.2 Printing a string

Printing a string to a window requires operating with another foreign C structure, the XFontStruct, which contains all of the information regarding the metrics of font that the X server will use to display our string.

This structure will be used to perform some computations that are required for the correct placement of the text in the window.

As we have seen with the window's attributes and the Graphic Context, we need a function that returns a pointer to this foreign structure, pointer that must be freed after using it.

A pointer to the XFontStruct is returned by [http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Font.html#v%3AloadQueryFont loadQueryFont], the interface to the X11 library function [http://www.tronche.com/gui/x/xlib/graphics/font-metrics/XLoadQueryFont.html XLoadQueryFont()].

XLoadQueryFont, which requires the X connection and the font name, will perform two distinct operations: load the needed font and return its id (performed by XLoadFont() which doesn't have a Haskell interface) and query the font to retrieve the XFontStruct (performed by [http://www.tronche.com/gui/x/xlib/graphics/font-metrics/XQueryFont.html XQueryFont] which does have a Haskell interface: [http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Font.html#v%3AqueryFont queryFont]).

The XFontStruct is needed by [http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Font.html#v%3AtextExtents textExtents], the interface to [http://www.tronche.com/gui/x/xlib/graphics/font-metrics/XTextExtents.html XTexteExtent()], and [http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Font.html#v%3AtextWidth textWidth], the interface to [http://www.tronche.com/gui/x/xlib/graphics/font-metrics/XTextWidth.html XTextWidth()].

These are their type signatures:

textExtents :: FontStruct -> String -> (FontDirection, Int32, Int32, CharStruct)
textWidth :: FontStruct -> String -> Int32

Given the FontStruct and the string to be printed, these functions will provide some valuable information. The values returned by the first one are related to font direction and vertical placement, while the second one will return the total width of the string to be printed with that specific font.

This information can be used with the graphic function that will actually draw the text on the window: [http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Misc.html#v%3AdrawImageString drawImageString], the interface to [http://www.tronche.com/gui/x/xlib/graphics/drawing-text/XDrawImageString.html XDrawImageString].

There are other version of this string, [http://www.tronche.com/gui/x/xlib/graphics/drawing-text/XDrawText.html XDrawText()] and [http://www.tronche.com/gui/x/xlib/graphics/drawing-text/XDrawText16.html XDrawText16()] for 16-bit characters.

We are going to use the first one because it will also use the foreground pixel set in the Graphic Context.

Its type signature is:

drawImageString :: Display -> Drawable -> GC -> Position -> Position -> String ->
IO ()

Finally we must remember to free the FontStruct with [http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Font.html#v%3AfreeFont freeFont] the interface to [http://www.tronche.com/gui/x/xlib/graphics/font-metrics/XFreeFont.html XFreeFont].

We can now write our function to print a string on a window, but first we need to make some small modifications to our drawInWin function, that will now take a string and will load and free the needed FontStruct to be passed to the new printString function:

drawInWin :: Display -> Window -> String -> IO ()
drawInWin dpy win str = do
 bgcolor <- initColor dpy "green"
 fgcolor <- initColor dpy "blue"
 gc <- createGC dpy win
 fontStruc <- loadQueryFont dpy "-misc-fixed-*-*-*-*-10-*-*-*-*-*-*-*"
 setForeground dpy gc bgcolor
 fillRectangle dpy win gc 0 0 200 100
 setForeground dpy gc fgcolor
 fillRectangle dpy win gc 2 2 196 96
 printString dpy win gc fontStruc str
 freeGC dpy gc
 freeFont dpy fontStruc

Here we are loading the "misc-fixed" font. You can select different fonts with the standalone utility:

xfontsel

As you see the FontStruct retrieved by loadQueryFont is used by printString and then freed.

So, let's look at printString:

printString :: Display
 -> Drawable
 -> GC
 -> FontStruct
 -> String
 -> IO ()
printString dpy d gc fontst str =
 do let strLen = textWidth fontst str
 (_,asc,_,_) = textExtents fontst str
 valign = (100 + fromIntegral asc) `div` 2
 remWidth = 200 - strLen
 offset = remWidth `div` 2
 fgcolor <- initColor dpy "white"
 bgcolor <- initColor dpy "blue"
 setForeground dpy gc fgcolor
 setBackground dpy gc bgcolor
 drawImageString dpy d gc offset valign str

In the "let" part we use textWidth and textExtents to set vertical and horizontal alignment: this is done by calculating the x and y coordinates for drawImageString. In this example text will be vertically and horizontally centered (take into account that I have also enlarged the window, whose width now is 200 pixels).

For a reference of the meaning of font ascent and descent, and the origins of the rectangle drawn by drawImageString read the par. 8.6 (Drawing Text) of the The Xlib Manual.

You may notice that printString takes a [http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Types.html#t%3ADrawable Drawable], which can be either a window or a pixmap (see below).

We can now rewrite our example and finally see some text printed in our window:

module Main where
import Data.Bits
import Graphics.X11.Xlib
import System.Exit (exitWith, ExitCode(..))
import System.Time
import Control.Concurrent (threadDelay)
 
main :: IO ()
main =
 do dpy <- openDisplay ""
 let dflt = defaultScreen dpy
 scr = defaultScreenOfDisplay dpy
 rootw <- rootWindow dpy dflt
 win <- mkUnmanagedWindow dpy scr rootw 0 0 200 100
 setTextProperty dpy win "The Clock" wM_NAME
 mapWindow dpy win
 drawInWin dpy win =<< date
 sync dpy False
 threadDelay (10 * 1000000)
 exitWith ExitSuccess
 
date :: IO String
date = do
 t <- toCalendarTime =<< getClockTime
 return $ calendarTimeToString t
 
drawInWin :: Display -> Window -> String -> IO ()
drawInWin dpy win str = do
 bgcolor <- initColor dpy "green"
 fgcolor <- initColor dpy "blue"
 gc <- createGC dpy win
 fontStruc <- loadQueryFont dpy "-misc-fixed-*-*-*-*-10-*-*-*-*-*-*-*"
 setForeground dpy gc bgcolor
 fillRectangle dpy win gc 0 0 200 100
 setForeground dpy gc fgcolor
 fillRectangle dpy win gc 2 2 196 96
 printString dpy win gc fontStruc str
 freeGC dpy gc
 freeFont dpy fontStruc
 
printString :: Display
 -> Drawable
 -> GC
 -> FontStruct
 -> String
 -> IO ()
printString dpy d gc fontst str =
 do let strLen = textWidth fontst str
 (_,asc,_,_) = textExtents fontst str
 valign = (100 + fromIntegral asc) `div` 2
 remWidth = 200 - strLen
 offset = remWidth `div` 2
 fgcolor <- initColor dpy "white"
 bgcolor <- initColor dpy "blue"
 setForeground dpy gc fgcolor
 setBackground dpy gc bgcolor
 drawImageString dpy d gc offset valign str
 
mkUnmanagedWindow :: Display
 -> Screen
 -> Window
 -> Position
 -> Position
 -> Dimension
 -> Dimension
 -> IO Window
mkUnmanagedWindow dpy scr rw x y w h = do
 let visual = defaultVisualOfScreen scr
 win <- allocaSetWindowAttributes $
 \attributes -> do
 set_override_redirect attributes True
 createWindow dpy rw x y w h 0 (defaultDepthOfScreen scr)
 inputOutput visual cWOverrideRedirect attributes
 return win
 
initColor :: Display -> String -> IO Pixel
initColor dpy color = do
 let colormap = defaultColormap dpy (defaultScreen dpy)
 (apros,real) <- allocNamedColor dpy colormap color
 return $ color_pixel apros

Since we are going to display the system time, I already added a "date" function and increased to width of our window.

Just give it a try. We are almost there. It's a clock, after all.

9 Updating a window

If you do not believe that now we have a system clock, just change the main function of the above example with the following two functions and try yourself:

main :: IO ()
main = do
 dpy <- openDisplay ""
 let dflt = defaultScreen dpy
 scr = defaultScreenOfDisplay dpy
 rootw <- rootWindow dpy dflt
 win <- mkUnmanagedWindow dpy scr rootw 0 0 200 100
 setTextProperty dpy win "The Clock" wM_NAME
 mapWindow dpy win
 updateWin dpy win
 
updateWin :: Display -> Window -> IO ()
updateWin dpy win = do
 drawInWin dpy win =<< date
 sync dpy False
 threadDelay (1 * 1000000)
 updateWin dpy win

This piece of code just adds the eternal recursive loop and, within this loop, reduces the thread block to 1 second. That's it.

Every second our window will be updated (redrawn).

Now, if you let the clock run for a while, you will notice that sometimes, during an update, the window sort of flickers.

This is due to the fact that we are drawing directly over the window. We need to adopt a better technique: we need to write to a pixmap first, and then copy the pixmap over the window.

Citing from [http://en.wikipedia.org/wiki/X_Window_System_core_protocol#Pixmaps_and_drawables Wikipedia]:

"A pixmap is a region of memory that can be used for drawing. Contrary to windows, the contents of pixmaps are not automatically shown on the screen. However, the content of a pixmap (or a part of it) can be transferred to a window and vice versa. This allows for techniques such as double buffering. Most of the graphical operations that can be done on windows can also be done on pixmaps. Windows and pixmaps are collectively named drawables, and their content data resides on the server."

This is not very difficult, and requires a very small change of our drawInWin function.

In order to create the pixmap we will use [http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Misc.html#v%3AcreatePixmap createPixmap], the interface to [http://www.tronche.com/gui/x/xlib/pixmap-and-cursor/XCreatePixmap.htm XCreatePixmap()], which takes the display connection, the drawable upon which the pixmap is created, the width, the height, and the depth of the screen.

We will then use [http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Misc.html#v%3AcopyArea copyArea], the interface to XCopyArea, to copy the pixmap over the window.

This is the copyArea type signature:

copyArea :: Display
 -> Drawable -> Drawable
 -> GC
 -> Position -> Position
 -> Dimension -> Dimension
 -> Position -> Position
 -> IO ()

that is to say:

  1. the display connection
  2. the origin and the destination drawables (our pixmap and our window

respectively)

  1. the x and y coordinates relative to the origin drawable upper left corner
  2. the width and the height of the area the be copied
  3. the x and y coordinates relative to the upper left corner of the destination drawable where the copied area must be placed

And we will eventually free the pixmap with [http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Misc.html#v%3AfreePixmap freePixmap], the interface to [http://www.tronche.com/gui/x/xlib/pixmap-and-cursor/XFreePixmap.html XFreePixmap].

Since our printString function may accept either a window or a pixmap, they both are drawables, all we need to do is to change drawInWin accordingly:

drawInWin :: Display -> Window -> String ->IO ()
drawInWin dpy win str = do
 bgcolor <- initColor dpy "green"
 fgcolor <- initColor dpy "blue"
 gc <- createGC dpy win
 fontStruc <- loadQueryFont dpy "-misc-fixed-*-*-*-*-10-*-*-*-*-*-*-*"
 p <- createPixmap dpy win 200 100 (defaultDepthOfScreen (defaultScreenOfDisplay dpy))
 setForeground dpy gc bgcolor
 fillRectangle dpy p gc 0 0 200 100
 setForeground dpy gc fgcolor
 fillRectangle dpy p gc 2 2 196 96
 printString dpy p gc fontStruc str
 copyArea dpy p win gc 0 0 200 100 0 0
 freeGC dpy gc
 freeFont dpy fontStruc
 freePixmap dpy p

Now, all graphic functions take now "p" and not "win". After copyArea everything is freed.

Our clock:

module Main where
import Data.Bits
import Graphics.X11.Xlib
import System.Exit (exitWith, ExitCode(..))
import System.Time
import Control.Concurrent (threadDelay)
 
main :: IO ()
main = do
 dpy <- openDisplay ""
 let dflt = defaultScreen dpy
 scr = defaultScreenOfDisplay dpy
 rootw <- rootWindow dpy dflt
 win <- mkUnmanagedWindow dpy scr rootw 0 0 200 100
 setTextProperty dpy win "Hello World - The Clock" wM_NAME
 mapWindow dpy win
 updateWin dpy win
 
updateWin :: Display -> Window -> IO ()
updateWin dpy win = do
 drawInWin dpy win =<< date
 sync dpy False
 threadDelay (1 * 1000000)
 updateWin dpy win
 
date :: IO String
date = do
 t <- toCalendarTime =<< getClockTime
 return $ calendarTimeToString t
 
drawInWin :: Display -> Window -> String ->IO ()
drawInWin dpy win str = do
 bgcolor <- initColor dpy "green"
 fgcolor <- initColor dpy "blue"
 gc <- createGC dpy win
 fontStruc <- loadQueryFont dpy "-misc-fixed-*-*-*-*-10-*-*-*-*-*-*-*"
 p <- createPixmap dpy win 200 100 (defaultDepthOfScreen (defaultScreenOfDisplay dpy))
 setForeground dpy gc bgcolor
 fillRectangle dpy p gc 0 0 200 100
 setForeground dpy gc fgcolor
 fillRectangle dpy p gc 2 2 196 96
 printString dpy p gc fontStruc str
 copyArea dpy p win gc 0 0 200 100 0 0
 freeGC dpy gc
 freeFont dpy fontStruc
 freePixmap dpy p
 
printString :: Display
 -> Drawable
 -> GC
 -> FontStruct
 -> String
 -> IO ()
printString dpy d gc fontst str =
 do let strLen = textWidth fontst str
 (_,asc,_,_) = textExtents fontst str
 valign = (100 + fromIntegral asc) `div` 2
 remWidth = 200 - strLen
 offset = remWidth `div` 2
 fgcolor <- initColor dpy "white"
 bgcolor <- initColor dpy "blue"
 setForeground dpy gc fgcolor
 setBackground dpy gc bgcolor
 drawImageString dpy d gc offset valign str
 
mkUnmanagedWindow :: Display
 -> Screen
 -> Window
 -> Position
 -> Position
 -> Dimension
 -> Dimension
 -> IO Window
mkUnmanagedWindow dpy scr rw x y w h = do
 let visual = defaultVisualOfScreen scr
 win <- allocaSetWindowAttributes $
 \attributes -> do
 set_override_redirect attributes True
 createWindow dpy rw x y w h 0 (defaultDepthOfScreen scr)
 inputOutput visual cWOverrideRedirect attributes
 return win
 
initColor :: Display -> String -> IO Pixel
initColor dpy color = do
 let colormap = defaultColormap dpy (defaultScreen dpy)
 (apros,real) <- allocNamedColor dpy colormap color
 return $ color_pixel apros

10 Dealing with events

Now try this.

In updateWin set threadDelay to something like:

 threadDelay (60 * 1000000)

Run the clock, switch to a console (with Alt+Ctrl+F1) and come back to the X server where the clock is running.

What happened? The window disappeared, and came back after being redrawn by drawInWin.

The problem is that our application does not respond to the events the X server is sending to our window. If a window is covered or anyway no more visible on the screen, when the covered area becomes visible again the X server will send to that window an Expose event, so that the application using that window may redraw it. Since our clock doesn't listen for any event, the window will not be redrawn till a new call to drawInWin is done.

Citing from Wikipedia:

"Events are packets sent by the server to a client to communicate that something the client may be interested in has happened. For example, an event is sent when the user presses a key or clicks a mouse button. Events are not only used for input: for example, events are sent to indicate the creation of new subwindows of a given window. Every event is relative to a window. For example, if the user clicks when the pointer is in a window, the event will be relative to that window. The event packet contains the identifier of that window."

The list of events a window will be reacting too is set as the [http://www.tronche.com/gui/x/xlib/window/attributes/event-and-do-not-propagate.html event mask] attribute of that window, and so may be set at creation time, as we have seen for the background pixel, or with XChangeWindowAttributes, or by using [http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Event.html#v%3AselectInput selectInput], the interface to XSelectInput.

In any case an event mask, [http://www.tronche.com/gui/x/xlib/window/attributes/event-and-do-not-propagate.html define as] "the bitwise inclusive OR of zero or more of the valid event mask bits", must be specified in a way very similar to the attribute mask specification.

This is the type signature of selectInput:

selectInput :: Display -> Window -> EventMask -> IO ()

The possible events to be included in the event must, separated by a bitwise inclusive OR, are listed here and [http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Types.html#3 here].

For a list of events types refer to the Xlib Manual.

In order to capture Expose events, we will need to set the [http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Types.html#v%3AexposureMask exposureMask] with something like this, right after the new window has been mapped, in our main function:

 selectInput dpy win exposureMask

This is all we need to do in order to configure the window in such a way that it will receive Expose events.

Our problem is far bigger than that, unfortunately. Our problem, indeed, is that we must update (redraw) our window either in the case of an Expose event is received and when a given amount of time is elapsed. This second requirement was met by blocking our program execution with threadDelay. But when our program is blocked it cannot receive any event.

But if we start listening for events and no Expose event happens, after some time is elapsed we must update our window anyhow.

How can we achieve this?

Just to explain our problem with other words, if we change, in the last example, main and updateWin to listen to events we end up with something like this:

main :: IO ()
main = do
 dpy <- openDisplay ""
 let dflt = defaultScreen dpy
 scr = defaultScreenOfDisplay dpy
 rootw <- rootWindow dpy dflt
 win <- mkUnmanagedWindow dpy scr rootw 0 0 200 100
 setTextProperty dpy win "The Clock" wM_NAME
 mapWindow dpy win
 selectInput dpy win (exposureMask .|. buttonPress)
 updateWin dpy win
 
updateWin :: Display -> Window -> IO ()
updateWin dpy win = do
 drawInWin dpy win =<< date
 sync dpy True
 allocaXEvent $ \e ->
 do nextEvent dpy e
 ev <- getEvent e
 putStrLn $ eventName ev
 updateWin dpy win

In main we added the selectInput call. Note that the event mask includes both Expose events, and mouse button press events (you may specify different types of events).

The second function, updateWin, required more modifications.

Now the sync call takes a True, and not a False any more. This means that when flushing the output buffer all events in the event queue will be discarded. This is necessary otherwise we are going to intercept previous events. For instance, if you change the Boolean to False, you will see a [http://www.tronche.com/gui/x/xlib/events/exposure/graphics-expose-and-no-expose.html NoExpose] event, that is the result of the application of XCopyArea in drawInWin.

Please note the use of [http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Event.html#v%3AallocaXEvent allocaXEvent], very similar to the use of allocaSetWindowAttributes, as shown by its type signature:

allocaXEvent :: (XEventPtr -> IO a) -> IO a

Within allocaXEvent we can use the pointer to the XEvent to:

  1. wait for the next event with

[http://hackage.haskell.org/packages/archive/X11/1.2.2/doc/html/Graphics-X11-Xlib-Event.html#v%3AnextEvent nextEvent], the interface to [http://www.tronche.com/gui/x/xlib/event-handling/manipulating-event-queue/XNextEvent.html XNextEvent];

  1. get the occurred event with getEvent (which requires X11-extras);
  2. convert the event in a string with eventName (which requires X11-extras);
  3. print the event name to the standard output: if we run our program from the command line, we can see the events received by our window.

I've also removed the threadDelay call. Guess why?

Just give it a run and you'll find out. If sync discards previous events, now nextEvent will block the program execution till an event occurs. If you don't press the mouse button over the window, or force an Expose event to occur, for instance by switching to a text console and back, the thread will be blocked in the safe foreign call to [http://www.tronche.com/gui/x/xlib/event-handling/manipulating-event-queue/XNextEvent.html XNextEvent], which, "if the event queue is empty, flushes the output buffer and blocks until an event is received".

This is the implementation of nextEvent:

-- | interface to the X11 library function @XNextEvent()@.
foreign import ccall safe "HsXlib.h XNextEvent"
 nextEvent :: Display -> XEventPtr -> IO ()

How can we unblock nextEvent after a given amount of time is elapsed?

10.1 Events and threads

One possible solution is to use a second thread to ask the X server to send an Expose event after some time.

This is the code:

module Main where
import Data.Bits
import Graphics.X11.Xlib
import Graphics.X11.Xlib.Extras
import System.Exit (exitWith, ExitCode(..))
import System.Time
import Control.Concurrent (threadDelay, forkIO)
 
main :: IO ()
main = do
 dpy <- openDisplay ""
 let dflt = defaultScreen dpy
 scr = defaultScreenOfDisplay dpy
 rootw <- rootWindow dpy dflt
 win <- mkUnmanagedWindow dpy scr rootw 0 0 200 100
 setTextProperty dpy win "The Clock" wM_NAME
 mapWindow dpy win
 selectInput dpy win (exposureMask .|. buttonPress)
 updateWin dpy win
 
updateWin :: Display -> Window -> IO ()
updateWin dpy win = do
 forkIO $ sendExposeEvent dpy win
 drawInWin dpy win =<< date
 sync dpy True
 allocaXEvent $ \e ->
 do nextEvent dpy e
 ev <- getEvent e
 putStrLn $ eventName ev
 updateWin dpy win
 
sendExposeEvent :: Display -> Window -> IO ()
sendExposeEvent dpy w =
 do threadDelay (1 * 1000000)
 allocaXEvent $ \e -> do
 setEventType e expose
 sendEvent dpy w False noEventMask e
 sync dpy False
 
date :: IO String
date = do
 t <- toCalendarTime =<< getClockTime
 return $ calendarTimeToString t
 
drawInWin :: Display -> Window -> String ->IO ()
drawInWin dpy win str = do
 bgcolor <- initColor dpy "green"
 fgcolor <- initColor dpy "blue"
 gc <- createGC dpy win
 fontStruc <- loadQueryFont dpy "-misc-fixed-*-*-*-*-10-*-*-*-*-*-*-*"
 p <- createPixmap dpy win 200 100 (defaultDepthOfScreen (defaultScreenOfDisplay dpy))
 setForeground dpy gc bgcolor
 fillRectangle dpy p gc 0 0 200 100
 setForeground dpy gc fgcolor
 fillRectangle dpy p gc 2 2 196 96
 printString dpy p gc fontStruc str
 copyArea dpy p win gc 0 0 200 100 0 0
 freeGC dpy gc
 freeFont dpy fontStruc
 freePixmap dpy p
 
printString :: Display
 -> Drawable
 -> GC
 -> FontStruct
 -> String
 -> IO ()
printString dpy d gc fontst str =
 do let strLen = textWidth fontst str
 (_,asc,_,_) = textExtents fontst str
 valign = (100 + fromIntegral asc) `div` 2
 remWidth = 200 - strLen
 offset = remWidth `div` 2
 fgcolor <- initColor dpy "white"
 bgcolor <- initColor dpy "blue"
 setForeground dpy gc fgcolor
 setBackground dpy gc bgcolor
 drawImageString dpy d gc offset valign str
 
mkUnmanagedWindow :: Display
 -> Screen
 -> Window
 -> Position
 -> Position
 -> Dimension
 -> Dimension
 -> IO Window
mkUnmanagedWindow dpy scr rw x y w h = do
 let visual = defaultVisualOfScreen scr
 win <- allocaSetWindowAttributes $
 \attributes -> do
 set_override_redirect attributes True
 createWindow dpy rw x y w h 0 (defaultDepthOfScreen scr)
 inputOutput visual cWOverrideRedirect attributes
 return win
 
initColor :: Display -> String -> IO Pixel
initColor dpy color = do
 let colormap = defaultColormap dpy (defaultScreen dpy)
 (apros,real) <- allocNamedColor dpy colormap color
 return $ color_pixel apros

This is going to work only if compiled with the ghc flag -threaded, otherwise it will not work.

A clear explanation of why can be found [http://haskell.org/ghc/docs/latest/html/libraries/base/Control-Concurrent.html#10 here].

10.2 A new nextEvent with asynchronous exceptions

This is a second solution and was proposed by Spencer Janssen.

It uses a version of nextEvent that will not block in a foreign call. An [http://haskell.org/ghc/docs/latest/html/libraries/base/Control-Exception.html#14 asynchronous exception] will be used to interrupt threadWaitRead.

This is the code:

import Prelude hiding (catch)
import Data.Bits
import Graphics.X11.Xlib
import Graphics.X11.Xlib.Extras
import System.Exit (exitWith, ExitCode(..))
import System.Time
import Control.Concurrent
import Control.Exception
import System.Posix.Types (Fd(..))
 
main :: IO ()
main = do
 dpy <- openDisplay ""
 let dflt = defaultScreen dpy
 scr = defaultScreenOfDisplay dpy
 rootw <- rootWindow dpy dflt
 win <- mkUnmanagedWindow dpy scr rootw 0 0 200 100
 setTextProperty dpy win "The Clock" wM_NAME
 mapWindow dpy win
 selectInput dpy win (exposureMask .|. buttonPress)
 updateWin dpy win
 
-- | A version of nextEvent that does not block in foreign calls.
nextEvent' :: Display -> XEventPtr -> IO ()
nextEvent' d p = do
 pend <- pending d
 if pend /= 0
 then nextEvent d p
 else do
 threadWaitRead (Fd fd)
 nextEvent' d p
 where
 fd = connectionNumber d
 
-- | The event loop
updateWin :: Display -> Window -> IO ()
updateWin dpy win = do
 t <- forkIO (block go)
 timer t
 where
 -- interrupt the drawing thread every so often
 timer t = do
 threadDelay (1 * 1000000)
 throwTo t (ErrorCall "done")
 timer t
 -- Continuously wait for a timer interrupt or an expose event
 go = do
 drawInWin dpy win =<< date
 catch (unblock $ allocaXEvent $ nextEvent' dpy) (const $ return ())
 go
 
sendExposeEvent :: Display -> Window -> IO ()
sendExposeEvent dpy w =
 do threadDelay (1 * 1000000)
 allocaXEvent $ \e -> do
 setEventType e expose
 sendEvent dpy w False noEventMask e
 sync dpy False
 
date :: IO String
date = do
 t <- toCalendarTime =<< getClockTime
 return $ calendarTimeToString t
 
drawInWin :: Display -> Window -> String ->IO ()
drawInWin dpy win str = do
 bgcolor <- initColor dpy "green"
 fgcolor <- initColor dpy "blue"
 gc <- createGC dpy win
 fontStruc <- loadQueryFont dpy "-misc-fixed-*-*-*-*-10-*-*-*-*-*-*-*"
 p <- createPixmap dpy win 200 100 (defaultDepthOfScreen (defaultScreenOfDisplay dpy))
 setForeground dpy gc bgcolor
 fillRectangle dpy p gc 0 0 200 100
 setForeground dpy gc fgcolor
 fillRectangle dpy p gc 2 2 196 96
 printString dpy p gc fontStruc str
 copyArea dpy p win gc 0 0 200 100 0 0
 freeGC dpy gc
 freeFont dpy fontStruc
 freePixmap dpy p
 
printString :: Display
 -> Drawable
 -> GC
 -> FontStruct
 -> String
 -> IO ()
printString dpy d gc fontst str =
 do let strLen = textWidth fontst str
 (_,asc,_,_) = textExtents fontst str
 valign = (100 + fromIntegral asc) `div` 2
 remWidth = 200 - strLen
 offset = remWidth `div` 2
 fgcolor <- initColor dpy "white"
 bgcolor <- initColor dpy "blue"
 setForeground dpy gc fgcolor
 setBackground dpy gc bgcolor
 drawImageString dpy d gc offset valign str
 
mkUnmanagedWindow :: Display
 -> Screen
 -> Window
 -> Position
 -> Position
 -> Dimension
 -> Dimension
 -> IO Window
mkUnmanagedWindow dpy scr rw x y w h = do
 let visual = defaultVisualOfScreen scr
 win <- allocaSetWindowAttributes $
 \attributes -> do
 set_override_redirect attributes True
 createWindow dpy rw x y w h 0 (defaultDepthOfScreen scr)
 inputOutput visual cWOverrideRedirect attributes
 return win
 
initColor :: Display -> String -> IO Pixel
initColor dpy color = do
 let colormap = defaultColormap dpy (defaultScreen dpy)
 (apros,real) <- allocNamedColor dpy colormap color
 return $ color_pixel apros