glfwSetInputMode with GLFW_CURSOR breaks on linux

I set up my game to grab/release the mouse when the player left-clicks on the window. This works perfectly until I click outside of the window: after that, the cursor refuses to disable. I’ve confirmed that GLFW detects the mouse click and sets the input mode to GLFW_CURSOR_DISABLED/NORMAL successfully, but the cursor stays visible and free to move around the screen.

To make sure this wasn’t a bug somewhere in the labyrinthine depths of my engine, I made a slight change to the hello world example on the LWJGL site, then tested it on Windows 7 and Fedora 21. It works correctly under Windows, but breaks as described on Fedora. Code follows:


package com.jiggawatt.maxclient;

import org.lwjgl.Sys;
import org.lwjgl.glfw.*;
import org.lwjgl.opengl.*;

import java.nio.ByteBuffer;

import static org.lwjgl.glfw.Callbacks.*;
import static org.lwjgl.glfw.GLFW.*;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.system.MemoryUtil.*;
 
public class GrabTest {
 
    // We need to strongly reference callback instances.
    private GLFWErrorCallback       errorCallback;
    private GLFWKeyCallback         keyCallback;
    private GLFWMouseButtonCallback mouseCallback;
    
    private boolean mouseGrabbed;
 
    // The window handle
    private long window;
 
    public void run() {
        System.out.println("Hello LWJGL " + Sys.getVersion() + "!");
 
        try {
            init();
            loop();
 
            // Release window and window callbacks
            glfwDestroyWindow(window);
            keyCallback.release();
            mouseCallback.release();
        } finally {
            // Terminate GLFW and release the GLFWerrorfun
            glfwTerminate();
            errorCallback.release();
        }
    }
 
    private void init() {
        // Setup an error callback. The default implementation
        // will print the error message in System.err.
        glfwSetErrorCallback(errorCallback = errorCallbackPrint(System.err));
 
        // Initialize GLFW. Most GLFW functions will not work before doing this.
        if ( glfwInit() != GL11.GL_TRUE )
            throw new IllegalStateException("Unable to initialize GLFW");
 
        // Configure our window
        glfwDefaultWindowHints(); // optional, the current window hints are already the default
        glfwWindowHint(GLFW_VISIBLE, GL_FALSE); // the window will stay hidden after creation
        glfwWindowHint(GLFW_RESIZABLE, GL_TRUE); // the window will be resizable
 
        int WIDTH = 300;
        int HEIGHT = 300;
 
        // Create the window
        window = glfwCreateWindow(WIDTH, HEIGHT, "Hello World!", NULL, NULL);
        if ( window == NULL )
            throw new RuntimeException("Failed to create the GLFW window");
 
        // Setup a key callback. It will be called every time a key is pressed, repeated or released.
        glfwSetKeyCallback(window, keyCallback = new GLFWKeyCallback() {
            @Override
            public void invoke(long window, int key, int scancode, int action, int mods) {
                if ( key == GLFW_KEY_ESCAPE && action == GLFW_RELEASE )
                    glfwSetWindowShouldClose(window, GL_TRUE); // We will detect this in our rendering loop
            }
        });
        
        // Mouse click callback (grabs/releases mouse)
        glfwSetMouseButtonCallback(window, mouseCallback =  
    		GLFWMouseButtonCallback((w, b, a, m)->{
    			if (a!=GLFW_PRESS)
    				return;
    			
    			int mode = mouseGrabbed ? GLFW_CURSOR_NORMAL : GLFW_CURSOR_DISABLED;
    			glfwSetInputMode(window, GLFW_CURSOR, mode);
    			mouseGrabbed = !mouseGrabbed;
    			
    			System.out.println("mouse "+(mouseGrabbed?"grabbed":"released"));
    		})
    	);
 
        // Get the resolution of the primary monitor
        ByteBuffer vidmode = glfwGetVideoMode(glfwGetPrimaryMonitor());
        // Center our window
        glfwSetWindowPos(
            window,
            (GLFWvidmode.width(vidmode) - WIDTH) / 2,
            (GLFWvidmode.height(vidmode) - HEIGHT) / 2
        );
 
        // Make the OpenGL context current
        glfwMakeContextCurrent(window);
        // Enable v-sync
        glfwSwapInterval(1);
 
        // Make the window visible
        glfwShowWindow(window);
    }
 
    private void loop() {
        // This line is critical for LWJGL's interoperation with GLFW's
        // OpenGL context, or any context that is managed externally.
        // LWJGL detects the context that is current in the current thread,
        // creates the ContextCapabilities instance and makes the OpenGL
        // bindings available for use.
        GLContext.createFromCurrent();
 
        // Set the clear color
        glClearColor(1.0f, 0.0f, 0.0f, 0.0f);
 
        // Run the rendering loop until the user has attempted to close
        // the window or has pressed the ESCAPE key.
        while ( glfwWindowShouldClose(window) == GL_FALSE ) {
            glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // clear the framebuffer
 
            glfwSwapBuffers(window); // swap the color buffers
 
            // Poll for window events. The key callback above will only be
            // invoked during this call.
            glfwPollEvents();
        }
    }
 
    public static void main(String[] args) {
        new GrabTest().run();
    }
 
}

Am I doing something terribly wrong? Have I fallen afoul of some GLFW quirk, or is this a bug in the library?

I cannot reproduce this on Windows 8.1 or OpenSUSE 13.2.

Upgraded to Fedora 22 today. Bug vanished! Whole lot of hours wasted on nothing.

Thanks for taking the time to look at my problem, Spasi. Hopefully it won’t pop up again.