Search code examples
c++gtkgtkmmcairogdk

Gtk+: How to set the cursor of a window from a Cairo context?


I have written the following code to set the cursor of a Gtk::Window from a Cairo::Context. When I run the program and move the cursor into the window, the cursor changes to a horizontal black line at the top, followed by some undefinable white shape at the bottom. I was expecting the cursor to change into a black 16×16 square. Why doesn't the cursor assume the shape I intended?

#include <gtkmm.h>

const int size = 16, hotspot = 0;

class Window : public Gtk::Window
{
  public:
    void change_cursor()
    {
      Glib::RefPtr<Gdk::Drawable> pixmap = Gdk::Pixmap::create(
          Glib::RefPtr<Gdk::Drawable>(), size, size, get_window()->get_depth());
      pixmap->set_colormap(get_window()->get_colormap());
      Cairo::RefPtr<Cairo::Context> context = pixmap->create_cairo_context();
      context->set_source_rgba(0, 0, 0, 0);
      context->rectangle(0, 0, size, size);
      context->fill();
      Glib::RefPtr<Gdk::Pixbuf> pixbuf
          = Gdk::Pixbuf::create(pixmap, 0, 0, size, size);
      Gdk::Cursor cursor(get_window()->get_display(), pixbuf, hotspot, hotspot);
      window->set_cursor(cursor);
    }
};

int main(int argc, char* argv[])
{
  Gtk::Main app(argc, argv);
  Window window;
  window.show_all();
  window.change_cursor();
  Gtk::Main::run(window);
  return 0;
}

When I draw the Gdk::Pixmap to the screen, it looks fine. When I draw the Gdk::Pixbuf to the screen, I get garbage.


Solution

  • I did not figure out the cause of the problem, but a way to work around it:

    1. Create an empty Gdk::Pixbuf.
    2. Create a Cairo::ImageSurface using the Gdk::Pixbuf's pixels as data buffer.
    3. Create a Cairo::Context from the Cairo::ImageSurface.
    4. Clear the Cairo::Context (this is important, because Gdk::Pixbuf's pixel data seem not to be initialized yet).

    The code looks like this:

    Glib::RefPtr<Gdk::Pixbuf> pixbuf
        = Gdk::Pixbuf::create(Gdk::COLORSPACE_RGB, true, 8, size, size);
    Cairo::RefPtr<Cairo::ImageSurface> surface
        = Cairo::ImageSurface::create(
              pixbuf->get_pixels(), Cairo::FORMAT_ARGB32,
              size, size, pixbuf->get_rowstride() );
    Cairo::RefPtr<Cairo::Context> context = Cairo::Context::create(surface);
    context->save();
    context->set_operator(Cairo::OPERATOR_CLEAR);
    context->paint();
    context->restore();
    

    When I now paint to that context and set the cursor from the Gdk::Pixbuf, I get almost what I want: the shape is fine, but Red and Blue are swapped. This can be fixed as outlined in Question 4291994 (How to write contents of a Cairo Image surface into a Gdk Pixbuf?):

      void fix_buffer_after_cairo(Glib::RefPtr<Gdk::Pixbuf> pixbuf)
      {
        guint8* pixels = pixbuf->get_pixels();
        int height = pixbuf->get_height();
        int width = pixbuf->get_width();
        int rowstride = pixbuf->get_rowstride();
    
        guint8 tmp;
        guint8* p;
        guint8* end;
    
        for (int j = height; j > 0; --j)
        {
          p = pixels;
          end = p + 4 * width;
          while (p < end)
          {
            tmp = p[0];
            if (G_BYTE_ORDER == G_LITTLE_ENDIAN)
            {
              p[0] = p[2]; p[2] = tmp;
            }
            else
            {
              p[0] = p[1]; p[1] = p[2]; p[2] = p[3]; p[3] = tmp;
            }
            p += 4;
          }
          pixels += rowstride;
        }
      }
    }