user1299518
user1299518

Reputation:

How to load windows cursors (.cur) in Linux using XLib and Xcursors?

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.

Upvotes: 4

Views: 5587

Answers (1)

user1299518
user1299518

Reputation:

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

Upvotes: 3

Related Questions