Cursors in Linux

I was doing some AWT stuff in Linux recently, and attempted to set a custom mouse cursor for a Window I created. The colors looked like they had been munged on the cursor for some reason, so I started to investigate. Eventually, I tried calling:

Toolkit.getDefaultToolkit().getMaximumCursorColors()

and was quite horrified to find it returning the number 2. Looking at the effects though, it does look like my custom cursor has been reduced to 2 colors.

Only 2 colors for a cursor? That doesn’t sound right. I know I’ve seen other applications in Linux use more colors than that for a cursor. Is there something I’m missing here, or is Java just behind the ball with Linux cursors?

BTW - tested the same code in Windows, and the cursor looks just fine.

Paul

It could be that the drivers or X server doesn’t support hw cursors with more than 2 colors.

You can pretty easily render the cursor yourself: set an empty cursor (or 1x1 translucent one) to hide the default cursor, register a MouseMotionListener, and render whatever your want at the mouse coordinates.
The benefit is that you have full control at what you want to render, it could be an image, a bunch of images (like a snake or something), etc, and it will work the same on all platforms.

Here’s a little something I came up with one winter evenining poking around with the Balls demo. It’s not going to work out of the box, but you can kind of get the idea. (sorry for the formatting)


/**
 * Simple custom cursor implementation.
 */
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.awt.image.BufferedImage;
import java.util.Arrays;

public class CustomCursor {

    static final int CURSOR_TAIL_LENGTH = 5;
    static final int MAX_CURSOR_TAIL_DISTANCE = 15;
    static final int TAIL_CATCHUP_SPEED = 3;

    Image cursorImage;

    Point lastMousePosition;
    Point cursorPositions[]; 

    int currentTailDistance;
    int lastUnchangedPosition;

    Timer cursorTimer;

    public CustomCursor(GameFrame parent, BufferedImage bi) {
      initCursorTail(bi, parent.getGraphicsConfig());
      parent.addMouseMotionListener(new MouseMotionAdapter() {
            public void mouseMoved(MouseEvent e) {
                synchronized (cursorPositions) {
                  cursorTimer.stop();
                  currentTailDistance = MAX_CURSOR_TAIL_DISTANCE;
                  lastMousePosition = e.getPoint();
                  System.arraycopy(cursorPositions, 0, 
                               cursorPositions, 1, 
                               cursorPositions.length - 1);
                  cursorPositions[0] = lastMousePosition;
                  lastUnchangedPosition = 0;
                }
            }
          });
    }

    void initCursorTail(BufferedImage image, GraphicsConfiguration gc) {
      lastMousePosition = new Point(0, 0);
      currentTailDistance = MAX_CURSOR_TAIL_DISTANCE;
      lastUnchangedPosition = 0;
      cursorPositions = new Point[CURSOR_TAIL_LENGTH*MAX_CURSOR_TAIL_DISTANCE];
      Arrays.fill(cursorPositions, lastMousePosition);
      cursorImage = gc.createCompatibleImage(image.getWidth(), image.getHeight(),
                                     Transparency.BITMASK);
      Graphics g = cursorImage.getGraphics();
      g.drawImage(image, 0, 0, null);

      cursorTimer = new Timer(30, new ActionListener() {
          public void actionPerformed(ActionEvent e) {
            synchronized (cursorPositions) {
                int newLastPos = lastUnchangedPosition + TAIL_CATCHUP_SPEED;
                if (newLastPos < cursorPositions.length) {
                  System.arraycopy(cursorPositions, lastUnchangedPosition, 
                               cursorPositions, newLastPos, 
                               cursorPositions.length - newLastPos);
                  Arrays.fill(cursorPositions, 
                            lastUnchangedPosition,
                            newLastPos,
                            cursorPositions[lastUnchangedPosition]);
                  lastUnchangedPosition = newLastPos;
                } else {
                  cursorTimer.stop();
                }
            }
          }
      });
    }

    public void render(Graphics g) {
      synchronized (cursorPositions) {
          float scale = 1f;
          int lastCursorPosition = currentTailDistance * CURSOR_TAIL_LENGTH - 1;
          for (int i = lastCursorPosition; i >= 0 ; i -= currentTailDistance) {
              // scale on the fly, this may be optimized to
              // render prescaled images
            g.drawImage(cursorImage,
                      (int)cursorPositions[i].getX(),
                      (int)cursorPositions[i].getY(),
                      (int)(cursorImage.getWidth(null) * scale),
                      (int)(cursorImage.getHeight(null) * scale),
                      null);
            scale -= .15;
          }
          cursorTimer.start();
      }
    }
    
    public void dispose() {
      cursorTimer.stop();
      cursorTimer = null;
    }
}


I understand the idea of a software rendered cursor, that’s just not what I’m looking for. And I think you may have missed my statement that other applications (heck, X itself) use hardware cursors with more than 2 colors on the same machine. I was hoping someone would either refute or confirm this as a generic problem with java on Linux before I go posting it as a bug with Sun.

I tried it a few months ago, seems that the linux java port doesn’t use multi-color cursors. multi-color and animated cursors are not very old, maybe the 1.5 will support this. can anyone confirm this?!

OK then, I guess it may be a bug or ‘implementation restriction’. Also, try asking it in awt forum (interaction with desktop is awt’s job)…

What linux have you tried this with? What X server version?