Hardware mouse problem, button not released?

I'm having some problem using the hardware mouse in JME.



The following procedure gives me the unexpexted result:


  1. Press down a mouse button inside the JME window
  2. Hold the button and move the cursor outside the window
  3. Release the button
  4. Move back in to the window
  5. PROBLEM: JME thinks the button is still pressed



    How do I solve it?



    Below is a simple test case (a little change in the TestHardwareMouse.java only). ( you need to press G for hardware mouse as in the original example)


package jmetest.input;

import java.util.logging.Level;
import java.util.logging.Logger;

import jmetest.renderer.TestEnvMap;

import com.jme.app.SimpleGame;
import com.jme.bounding.BoundingBox;
import com.jme.image.Texture;
import com.jme.input.AbsoluteMouse;
import com.jme.input.InputHandler;
import com.jme.input.KeyBindingManager;
import com.jme.input.KeyInput;
import com.jme.input.MouseInput;
import com.jme.input.action.InputAction;
import com.jme.input.action.InputActionEvent;
import com.jme.math.Vector3f;
import com.jme.renderer.Renderer;
import com.jme.scene.Controller;
import com.jme.scene.Spatial;
import com.jme.scene.Text;
import com.jme.scene.shape.Box;
import com.jme.scene.state.AlphaState;
import com.jme.scene.state.LightState;
import com.jme.scene.state.TextureState;
import com.jme.util.TextureManager;

/**
 * <code>TestHardwareMouse</code>
 *
 * @author Rikard Herlitz (MrCoder)
 */
public class TestHardwareMouse extends SimpleGame {
    private static final Logger logger = Logger
            .getLogger(TestHardwareMouse.class.getName());
   
   private AbsoluteMouse mouse;
   private Box spinningBox;
   private boolean useHardwareCursor;
   private Text currentTypeText;
   private final long SLEEPING_TIME = 20;

   public static void main(String[] args) {
      TestHardwareMouse app = new TestHardwareMouse();
      app.setDialogBehaviour(ALWAYS_SHOW_PROPS_DIALOG);
      app.start();
   }

   protected void simpleUpdate() {
      //Switch between software/hardware mousecursor
      if (KeyBindingManager.getKeyBindingManager().isValidCommand("g", false)) {
         useHardwareCursor = !useHardwareCursor;
         if (useHardwareCursor) {
            //currentTypeText.print("Current cursor type: [HARDWARE]");
            mouse.setCullMode(Spatial.CULL_ALWAYS);
            MouseInput.get().setHardwareCursor(TestHardwareMouse.class.getClassLoader().getResource("jmetest/data/cursor/cursor1.png"));
            //correction due to different hotspot positions between hardware/software. not needed when using only hardware etc.
            MouseInput.get().setCursorPosition((int) mouse.getLocalTranslation().x - mouse.getImageWidth() / 2,
                                       (int) mouse.getLocalTranslation().y + mouse.getImageHeight() / 2);
         }
         else {
            //currentTypeText.print("Current cursor type: [SOFTWARE]");
            mouse.setCullMode(Spatial.CULL_NEVER);
            MouseInput.get().setCursorVisible(false);
         }
      }

      //Sleep to simulate game fps
      try {
         Thread.sleep(SLEEPING_TIME);
      } catch (InterruptedException e) {
         logger.logp(Level.SEVERE, this.getClass().toString(), "simpleUpdate()", "Exception",
                    e);
      }
   }

   protected void simpleInitGame() {
      input = new InputHandler();

      cam.getLocation().set(0, 0, 150);
      cam.update();

      //Setup software mouse
      mouse = new AbsoluteMouse("Mouse Input", display.getWidth(), display.getHeight());
      mouse.registerWithInputHandler(input);
      TextureState cursor = display.getRenderer().createTextureState();
      cursor.setTexture(TextureManager.loadTexture(
            TestHardwareMouse.class.getClassLoader().getResource("jmetest/data/cursor/cursor1.png"),
            Texture.MM_LINEAR, Texture.FM_LINEAR));
      mouse.setRenderState(cursor);
      AlphaState as1 = display.getRenderer().createAlphaState();
      as1.setBlendEnabled(true);
      as1.setSrcFunction(AlphaState.SB_SRC_ALPHA);
      as1.setDstFunction(AlphaState.DB_ONE_MINUS_SRC_ALPHA);
      as1.setTestEnabled(true);
      as1.setTestFunction(AlphaState.TF_GREATER);
      mouse.setRenderState(as1);
      rootNode.attachChild(mouse);

      //Setup dummybox to show that we are running
      spinningBox = new Box("SpinBox", new Vector3f(), 10, 10, 10);
      spinningBox.setModelBound(new BoundingBox());
      spinningBox.updateModelBound();
      TextureState ts = display.getRenderer().createTextureState();
      Texture t0 = TextureManager.loadTexture(
            TestEnvMap.class.getClassLoader().getResource(
                  "jmetest/data/images/Monkey.jpg"),
            Texture.MM_LINEAR_LINEAR,
            Texture.FM_LINEAR);
      t0.setWrap(Texture.WM_WRAP_S_WRAP_T);
      ts.setTexture(t0);
      spinningBox.setRenderState(ts);
      rootNode.attachChild(spinningBox);
      //Make the box spin around
      spinningBox.addController(new Controller() {
         float spinTime = 0;
         public void update(float time) {
            spinTime += time;
            spinningBox.getLocalRotation().fromAngles(spinTime, spinTime, spinTime);
         }
      });


        //create an action to shown button activity
        InputAction buttonAction = new InputAction() {
            public void performAction( InputActionEvent evt ) {
                String actionString;
                if ( !evt.getTriggerAllowsRepeats() ) {
                    actionString = evt.getTriggerPressed() ? "pressed" : "released";
                } else {
                    actionString = "down";
                }
                currentTypeText.print( evt.getTriggerDevice() + " " + evt.getTriggerName() + " (" + evt.getTriggerIndex() + ":" + evt.getTriggerCharacter() + ") " +
                        actionString + " on " + timer.getTime() );
            }
        };
        //register the action with all devices (mouse, keyboard, joysticks, etc) for all buttons
        input.addAction( buttonAction, InputHandler.DEVICE_ALL, InputHandler.BUTTON_ALL, InputHandler.AXIS_NONE, false );
    
       
      //Setup keybinding and help text
      KeyBindingManager.getKeyBindingManager().set("g", KeyInput.KEY_G);

      Text helpText = new Text("Text", "Using forced sleep(" + SLEEPING_TIME + ") to clearly show the difference...");
      helpText.setRenderQueueMode(Renderer.QUEUE_ORTHO);
      helpText.setLightCombineMode(LightState.OFF);
      helpText.setLocalTranslation(new Vector3f(0, display.getHeight() - 20, 1));
      fpsNode.attachChild(helpText);

      Text helpText2 = new Text("Text", "Key 'G': Switch between software/hardware mouse cursor");
      helpText2.setRenderQueueMode(Renderer.QUEUE_ORTHO);
      helpText2.setLightCombineMode(LightState.OFF);
      helpText2.setLocalTranslation(new Vector3f(0, 60, 1));
      fpsNode.attachChild(helpText2);

      currentTypeText = new Text("Text", "Current cursor type: [SOFTWARE]");
      currentTypeText.setRenderQueueMode(Renderer.QUEUE_ORTHO);
      currentTypeText.setLightCombineMode(LightState.OFF);
      currentTypeText.setLocalTranslation(new Vector3f(0, 40, 1));
      fpsNode.attachChild(currentTypeText);
   }
}



Thanks for helping! :)

The problem is based on how input events are given by lwjgl and is not really a bug (you have events that happen outside the scope of your app.)  iirc, I solved it in my own app by writing a "focus" listener that was triggered when the display received/lost focus using isActive.

Ok, when I will do something like that. Checking if the display loses focus and if so then release the mouse buttons manually.



Thanks for the fast answer!

One more question if you dont mind… how do I check if the mouse just leaves the window?



I tried the display.isActive(), but that only checks if the window lost focus ( user has clicked on another window ). How do I check if the mouse is outside the window ( and user has not clicked on anything so the jME window still has focus)?



I'm using hardware mouse and windowed mode.

You might be able to use mouse position to do that… or you can use jme in a Canvas instead and use a mouse listener.

I've actually tried mouse position but that didn't work as expected. I found no way to get the coordinates or mouse updates when the mouse is outside the window (probably the same reason as I could not get mouse button release) so the only solution I saw was to check if it passes the border of the window. But that didn't work good because if you move the mouse quickly when it will miss the border and won't update correctly. (expanding the border region would only give other problems)



Does jme work good in a Canvas? I've read in some threads that it's buggy and for example it does not work in mac?

I works fine in a canvas.  When I was an NCsoft that is how we built our tools and they worked fine on Mac as well.