I got a bug report that my pygtk application FSlint was not setting the busy mouse cursor during long operations. Now the interface is responsive during long operations in that one can click a "stop" button, so I was advised to use the "Busy-Interactive" mouse cursor as indicated in the HIG. This proved to be much more difficult that it should be however.

First lets have a look at the mouse cursors available to X applications. One can use the Xcursor library directly, which allows for full color icon themes, or more usually one can use a toolkit like QT or GTK+ to indirectly select the (themed) X mouse cursor. Note QT has some inbuilt cursors that mostly map to equivalents in X, but the last 4 in the table below do not. GTK+ does not have inbuilt cursors and just makes the predefined X cursors available.

Note the bitmap cursors below are 2 color, not single color, because they are constructed from 2 bitmaps. The first defines what bits are to be set to the foreground and background colours, and the second determines what bits to display.

QTX/GDK
Qt::ArrowCursor LEFT_PTR
Qt::UpArrowCursor SB_UP_ARROW
Qt::CrossCursor CROSSHAIR
Qt::IBeamCursor XTERM
Qt::WaitCursor WATCH
Qt::PointingHandCursor HAND2
Qt::WhatsThisCursor QUESTION_ARROW
Qt::SizeVerCursor SB_V_DOUBLE_ARROW
Qt::SizeHorCursor SB_H_DOUBLE_ARROW
Qt::SizeBDiagCursor BOTTOM_LEFT_CORNER
Qt::SizeFDiagCursor BOTTOM_RIGHT_CORNER
Qt::SizeAllCursor FLEUR
Qt::SplitVCursor can use SB_V_DOUBLE_ARROW
Qt::SplitHCursor can use SB_H_DOUBLE_ARROW
Qt::BusyCursor  
Qt::ForbiddenCursor
Qt::OpenHandCursor
Qt::ClosedHandCursor

X has lots more (less useful) cursors available to QT and GTK+:
montage -frame 5x5 -geometry '20x20+0+0>' -tile 13x5 *.png montage.png

So one can see from the table above, that if I was using QT, selecting the "Busy-Interactive" mouse pointer would be trivial, but unfortunately there is no direct support in GTK+ or X. Now a themed version of this cursor is available to applications as can be seen for example in firefox, where it sets the "Busy-Interactive" cursor any time it loads a page. Looking in my cursor theme directories, shows the cursor I'm trying to set is "left_ptr_watch"

/usr/share/icons/Bluecurve/cursors/left_ptr_watch
/usr/share/icons/Bluecurve/cursors/08e8e1c95fe2fc01f976f1e063a24ccd -> left_ptr_watch
/usr/share/icons/Human/cursors/left_ptr_watch
/usr/share/icons/Human/cursors/08e8e1c95fe2fc01f976f1e063a24ccd
So what actually happens is that for every custom bitmap cursor that an application sets, X takes a hash of the bits and sees whether there is a corresponding themed cursor for that hash. This method would allow one to theme the cursors for a closed source application (Netscape?). Unfortunately X didn't make it easy for newer or open source applications by also defining a new "left_ptr_watch" standard cursor. Perhaps there is some reason why new standard cursors can not be added?

So how do you find out the exact bits to set for the bitmap cursor? Well X uses the "XCURSOR_DISCOVER" environment variable to display the bitmap cursor an application sets and the corresponding hash. So running firefox with this enabled gives:

export XCURSOR_DISCOVER=1
firefox http://www.pixelbeat.org/ | tr ' ' .

Cursor.image.name:.08e8e1c95fe2fc01f976f1e063a24ccd
................................
................................
..*.............................
..**............................
..***...........................
..****..........................
..*****.........................
..******........................
..*******.......................
..********.***..................
..*****....***..................
..**.**...*.*.*.................
..*...**..***.**................
......**..*...*.................
.......**..***..................
.......**..***..................
...
This shows the 32x32 cursor that corresponds to the hash. Now to create a bitmap in X to set the cursor from, one must generate the appropriate XBM format data to pass to the GDK function bitmap_create_from_data(). This is easily done using the standard X "bitmap" utility.

I converted this XBM string into a more concise python string, for use in the following pygtk program, which shows how to set the themed left_ptr_watch mouse cursor.

import os
os.environ['XCURSOR_DISCOVER']='1' #Turn on logging in Xlib

import gtk
w=gtk.Window()
w.realize()

# X includes a left_ptr_watch cursor but doesn't have a name for it.
# Instead it links the hash of the corresponding mozilla bitmap cursor to it.
# redhat-artwork links 08e8e1c95fe2fc01f976f1e063a24ccd to it's version
# of left_ptr_watch. So since X does the link we can assume it's always present.
mozilla_left_ptr_watch = "\
\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x0c\x00\x00\x00\
\x1c\x00\x00\x00\x3c\x00\x00\x00\x7c\x00\x00\x00\xfc\x00\x00\x00\
\xfc\x01\x00\x00\xfc\x3b\x00\x00\x7c\x38\x00\x00\x6c\x54\x00\x00\
\xc4\xdc\x00\x00\xc0\x44\x00\x00\x80\x39\x00\x00\x80\x39"+"\x00"*66

left_ptr_watch=mozilla_left_ptr_watch

try:
    pix = gtk.gdk.bitmap_create_from_data(None, left_ptr_watch, 32, 32)
    color = gtk.gdk.Color()
    LEFT_PTR_WATCH=gtk.gdk.Cursor(pix, pix, color, color, 2, 2)
    #Note mask is ignored when doing the hash
except TypeError:
    #http://bugzilla.gnome.org/show_bug.cgi?id=103616 #older pygtks
    #http://bugzilla.gnome.org/show_bug.cgi?id=318874 #pygtk-2.8.[12] (breezy)
    LEFT_PTR_WATCH=None #default cursor

w.window.set_cursor(LEFT_PTR_WATCH)

w.connect("destroy", gtk.main_quit)
w.show_all()
gtk.main()
The "fun" didn't stop there however, as I found it quite difficult to set this mouse cursor for various GTK+ widgets. The reason is that there is no set_cursor() call for GTK widgets, and one must instead find any visible GDK window(s) associated with the widget and set the cursor for those. Personally I think that should be encapsulated within the GTK widget, and it should set the cursor on the appropriate GDK window(s). Also restoring a widget's mouse cursor to the default is not robust as one can't read the current mouse pointer to restore later. So I had to hard code the pointers to restore the widgets with. You can see the details of how I set and restored the mouse cursors for the GtkEntry, GtkPaned, GtkSpinButton and GtkTextView widgets by searching for the "set_cursor" function in fslint-gui

[Update Feb 2008: I notice that Fedora 8 now maps the WATCH cursor which is easily selectable and usually displayed as an hourglass, to LEFT_PTR_WATCH. So that means an hourglass is never displayed now?

/usr/share/icons/Bluecurve/cursors/_watch-old_
/usr/share/icons/Bluecurve/cursors/watch -> left_ptr_watch
/usr/share/icons/Bluecurve/cursors/08e8e1c95fe2fc01f976f1e063a24ccd -> left_ptr_watch
I also notice that the X `bitmap` utility referred to above is not installed by default in Fedora 8 at least.]

© May 23 2007