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.
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
<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