4

I'm trying to load a .cur file via Xcursor library and i just can't make it work. I'm working with linux (Bodhi + Lubuntu) on virtualbox. Here's my code:

my includes

// GLFW
#define GLFW_DLL       // use the GLFW .dll
#include "gl/GLFW/glfw3.h"

...

#if defined(__linux__)
   // GLFW native expoose
   #define GLFW_EXPOSE_NATIVE_X11        // expose X11 and GLX context (linux)
   #define GLFW_EXPOSE_NATIVE_GLX
   #include "gl/GLFW/glfw3native.h"      // for low-level expose
   // X Window
   #include <X11/X.h> 
   #include <X11/Xlib.h> 
   #include <X11/Xutil.h>
   #include <X11/Xcursor/Xcursor.h>
#endif

in program, after GLFW window creation

#if defined(__linux__)
   Display* dpy = glfwGetX11Display();
   Window xwindow = glfwGetX11Window(window);
   Cursor crs = XcursorFilenameLoadCursor(dpy,"somefolder/default.cur");
   XDefineCursor(dpy,xwindow,crs);
#endif

...

#if defined(__linux__)
   XFreeCursor(dpy,crs);
#endif

Any obvious error? I can't really find sources about Xcursor and i'm close to switch to typical OpenGL textures and get over it.

NOTE: I'm already disabled Virtualbox integrated mouse, so the mouse is entirely on guest machine and i run it fullscreen.

  • 1
    You code works here (Archlinux, with xmonad or Xfce/xfwm and a cursor from [there](http://xfce-look.org/content/show.php/Arch+Cursor+Theme+%28simple%29?content=135902)). – Leiaz Sep 29 '14 at 14:58
  • @Leiaz, thank you for the comment. I assume the cursor you tested with is `.xbm` (x window bitmap) ? From documentation [here](http://linux.die.net/man/3/xcursorfilenameloadcursor) i can't really find out if _Xcursor_ supports `.cur` or not. Thank you for the effort. –  Sep 30 '14 at 17:12
  • Oh .. that is probably the problem. `file` says it's a "X11 cursor". `.cur` seems to be a Windows format. The Xcursor file format is defined in the man page, I don't think it's possible to use another format directly. There is a [`xcursorgen`](http://linux.die.net/man/1/xcursorgen) utility to create a Xcursor from PNG file(s). – Leiaz Sep 30 '14 at 19:11
  • @Leiaz, thank you. I had the impression `.cur` are compatible with Xcursor, but they're not. Even `.xbm` or `.xpm` were not loaded. Your cursors loaded as well :) although i can't find out what they are or how to create them (i mean, specs) The only thing for sure their header start with `Xcur..`. Convert PNGs to `xcursor` i don't find it really good since i want to be able to use the same cursor in Windows/Linux and don't like really to provide hotspots as well; perhaps some internal conversion from `.cur` to xcurso` might be the way once i find out the `xcursor` format. I'll post my results. –  Oct 02 '14 at 19:05
  • There was a problem in Oracle Virtualbox `not able to show hardware cursors on OpenGL context`; they've shown under it and this is known issue (as i learnt) for quite some time now. This gave the impression of cursors-not-loaded. I just switched to VMware and it doesn't suffering from this. –  Oct 02 '14 at 19:10
  • They *are* "X11 cursor" : magic bytes : CARD32 'Xcur' (0x58, 0x63, 0x75, 0x72) (a CARD32 is a 32bit unsigned int). As written in the man page : the [documentation you linked](http://linux.die.net/man/3/xcursor), in the section "CURSOR FILES", the file format is described there :) If you don't want to create a file, look in Xcursor.h : `XCursorImageCreate`/`Destroy` `XCursorImageLoadCursor` – Leiaz Oct 02 '14 at 20:25
  • @Leiaz, yes i found it and i'm already on `XCursorImageCreate` approach, it seems the most cross-platform way. Thank you, i'll post my results later for the ones encountered the same problem. –  Oct 02 '14 at 20:40

1 Answers1

3

Since i solved my problem i decided it's a good idea to post my results, in hope of helping someone trying to make his application more cross-platform. My code follows, and explanation follows the code.

ESSENTIAL

  • X11 development headers package (etc. libx11-dev)
  • Xcursor development headers package (etc. libxcursor-dev)
  • A portable <stdint.h> header, like this one.

CODE

...
// to store colors
struct COLOR {
   uint8_t r,g,b,a;
   COLOR() {
      this->r = this->g = this->b = this->a = 0;
   }
   COLOR(uint8_t r,uint8_t g,uint8_t b) {
      this->r = r;
      this->g = g;
      this->b = b;
      this->a = 255;
   }
   bool operator == (COLOR c) const {
      return (r == c.r && g == c.g && b == c.b);
   }
};

size_t get_bit(int32_t number,int8_t position) {
   size_t bitmask = 1 << position;
   return (number & bitmask) ? 1 : 0;
}

....
// load cursor
#if defined(_WIN32)
   // etc. use standard WinApi code, (HCURSOR)LoadImage(..) and such
#endif
#if defined(__linux__)

  Display* display = XOpenDisplay(NULL);

  string filename = "mycursor.cur";    // <-- add your .cur cursor here
  char buf[4];
  FILE* fp = fopen(filename.c_str(),"rb");
  fread(buf,1,2,fp);    // reserved. always 0
  fread(buf,1,2,fp);    // image type; we're only interested for .cur (=2)
  int16_t imagetype = buf[0] | (buf[1]<<8);
  if (imagetype!=2) {
     // error:  file is not a valid .cur file
     return;
  }
  fread(buf,1,2,fp);    // number of images
  // we're only interested in the first image
  fread(buf,1,1,fp);    // width  (0 to 255; 0=means 256 width)
  int8_t width = (buf[0]==0 ? 256 : buf[0]);
  fread(buf,1,1,fp);    // height (0 to 255; 0=means 256 height)
  int8_t height = (buf[0]==0 ? 256 : buf[0]);
  fread(buf,1,1,fp);    // number of colors in palette (0 for no palette)
  fread(buf,1,1,fp);    // reserved. should be 0
  fread(buf,1,2,fp);    // hotspot x
  int16_t hotspot_x = buf[0] | (buf[1]<<8);
  fread(buf,1,2,fp);    // hotspot y
  int16_t hotspot_y = buf[0] | (buf[1]<<8);
  fread(buf,1,4,fp);    // image data in bytes
  fread(buf,1,4,fp);    // offset to image data

  // Now we need to verify if image in .cur is BMP or PNG (Vista+)
  // We can't just check 'magic' since if it's BMP, the header will be missing (PNG is whole)
  // So we search for PNG magic; if doesnt exist, we have a BMP!
  // NOTE:  for simplicity we go only for BMP for the moment. 
  //        So just check if 'biSize' is 40 (Windows NT & 3.1x or later)

  // BITMAPINFOHEADER 
  fread(buf,1,4,fp);    // biSize
  int32_t biSize = (buf[0]&0xff) | (buf[1]<<8) | (buf[2]<<16) | (buf[3]<<24);
  if (biSize!=40) {
     // error:  file does not contain valid BMP data;
     return;
  }
  fread(buf,1,4,fp);    // biWidth
  int32_t biWidth = (buf[0]&0xff) | (buf[1]<<8) | (buf[2]<<16) | (buf[3]<<24);
  fread(buf,1,4,fp);    // biHeight (if positive => bottom-up, if negative => up-bottom)
  int32_t biHeight = (buf[0]&0xff) | (buf[1]<<8) | (buf[2]<<16) | (buf[3]<<24);
  fread(buf,1,2,fp);    // biPlanes
  fread(buf,1,2,fp);    // biBitCount         
  int16_t biBitCount = (buf[0]&0xff) | (buf[1]<<8) | (buf[2]<<16) | (buf[3]<<24);
  if (biBitCount!=24 && biBitCount!=32) {
     // error:  only 24/32 bits supported;
     return;
  }
  fread(buf,1,4,fp);    // biCompression
  int32_t biCompression = (buf[0]&0xff) | (buf[1]<<8) | (buf[2]<<16) | (buf[3]<<24);
  // we want only uncompressed BMP data
  if (biCompression!=0 /*BI_RGB*/ ) {
     // error:  file is compressed; only uncompressed BMP is supported;
     return;
  }
  fread(buf,1,4,fp);    // biSizeImage
  fread(buf,1,4,fp);    // biXPelsPerMeter
  fread(buf,1,4,fp);    // biYPelsPerMeter
  fread(buf,1,4,fp);    // biClrUsed
  fread(buf,1,4,fp);    // biClrImportant

  // DECODE IMAGE
  uint8_t origin = (biHeight>0 ? 0 : 1);    // 0=bottom-up, 1=up-bottom
  // there are cases where BMP sizes are NOT the same with cursor; we use the cursor ones
  biWidth = width;
  biHeight = height;

  COLOR* pixels = new COLOR[biWidth * biHeight];
  for(int32_t y=0;y<biHeight;y++) {
     for(int32_t x=0;x<biWidth;x++) {
        uint32_t offset = ((origin==1?y:biHeight-1-y)*biWidth)+x;
        // read pixels by number of bits
        switch(biBitCount) {
           // 24-bit
           case 24:
              fread(buf,1,3,fp); 
              pixels[offset] = COLOR(buf[0],buf[1],buf[2]);
           break;
           // 32-bit
           case 32:
              fread(buf,1,4,fp);
              pixels[offset] = COLOR(buf[0],buf[1],buf[2]);
              pixels[offset].a = buf[3];
           break;
        }
     }
  }

  // read mask
  // mask is 1-bit-per-pixel for describing the cursor bitmap's opaque and transparent pixels.
  // so for 1 pixel we need 1 bit, for etc. 32 pixels width, we need 32*1 bits (or 4 bytes per line)
  // so for etc. 32 pixels height we need 4*32 = 128 bytes or [mask bytes per line * height]
  uint16_t mask_bytes_per_line = biWidth / 8;
  uint16_t mask_bytes = mask_bytes_per_line * biHeight;
  char* mask = new char[mask_bytes];

  fread(mask,1,mask_bytes,fp);
  fclose(fp);

  // reverse every [mask_bytes_per_line] bytes; (etc. for 4 bytes per line do:  0,1,2,3 => 3,2,1,0) -> you really need to ask Microsoft for this 8)
  char rmask[4];
  for(uint16_t x=0;x<mask_bytes;x+=mask_bytes_per_line) {
     for(uint16_t r=0;r<mask_bytes_per_line;r++) {
        rmask[r] = mask[x+mask_bytes_per_line-1-r];
     }
     // copy the reversed line
     for(uint16_t r=0;r<mask_bytes_per_line;r++) {
        mask[x+r] = rmask[r];
     }
  }

  // now extract all bits from mask bytes into new array (equal to width*height)
  vector<uint8_t> bits;
  for(uint16_t x=0;x<mask_bytes;x++) {
     for(uint8_t b=0;b<8;b++) {
        bits.push_back( get_bit(mask[x],b) );
     }
  }
  delete[] mask;
  // reverse vector (?)
  reverse(bits.begin(),bits.end());

  // now the bits contains =1 (transparent) and =0 (opaque) which we use on cursor directly
  XcursorImage* cimg = XcursorImageCreate(biWidth,biHeight);
  cimg->xhot = hotspot_x;
  cimg->yhot = hotspot_y;
  // create the image
  for(int32_t y=0;y<biHeight;y++) {
     for(int32_t x=0;x<biWidth;x++) {
        uint32_t offset = (y*biWidth)+x;
        COLOR pix = pixels[offset];
        cimg->pixels[offset] = ((bits[offset]==1?0:pix.a)<<24) + (pix.r<<16) + (pix.g<<8) + (pix.b);
     }
  }
  // create cursor from image and release the image
  Cursor cursor = XcursorImageLoadCursor(display,cimg);
  XcursorImageDestroy(cimg);

  ...
  // set the cursor
  XDefineCursor(display,yourwindow,cursor);
  XFlush(display);

  ...
  // free cursor
  if (cursor!=None) {
     XFreeCursor(display,cursor);
  }

The above code takes a Windows .cur cursor file and creates an Xcursor for use in X11 window system. Of course, there are lots of limitations to my .cur format handling but one can easily add his own improvements to the above code (like say supporting 8-bit cursors). The above code not only take care of alpha-transparency but also 32-bit alpha-blended cursors