Skip to content

Keyboard & Mouse

The keyboard and mouse are where most interaction lives. This page walks through reading keys, receiving typed text, tracking mouse buttons and cursor position, handling the scroll wheel, grabbing the cursor for camera control, swapping cursor shapes, and touching the system clipboard.

Everything here assumes you have a $window from Creating a Window, and it helps to have read the Input & Events overview first, since the callback idiom and the GLFW_PRESS / GLFW_MOD_* vocabulary are explained there. To see it all running at once:

php examples/08_input_and_events.php

Reading the keyboard

For keys you care about continuously, such as movement, poll their state each frame with glfwGetKey. It returns GLFW_PRESS or GLFW_RELEASE:

if (glfwGetKey($window, GLFW_KEY_W) === GLFW_PRESS) {
    $ship->moveForward();
}
if (glfwGetKey($window, GLFW_KEY_LEFT_SHIFT) === GLFW_PRESS) {
    $ship->boost();
}

Keys are named after their physical position on a US keyboard layout, so GLFW_KEY_W is always the key where W sits, regardless of the user's language layout. That is exactly what you want for movement bindings. There is a token for every key: letters (GLFW_KEY_A), digits (GLFW_KEY_0), function keys (GLFW_KEY_F1), the arrows (GLFW_KEY_UP), and named keys like GLFW_KEY_ESCAPE, GLFW_KEY_ENTER, GLFW_KEY_SPACE, and GLFW_KEY_TAB.

For discrete key events, such as a menu shortcut or a single jump, register a callback with glfwSetKeyCallback. Your closure receives ($key, $scancode, $action, $mods):

glfwSetKeyCallback($window, function ($key, $scancode, $action, $mods) use ($window) {
    if ($key === GLFW_KEY_ESCAPE && $action === GLFW_PRESS) {
        glfwSetWindowShouldClose($window, GL_TRUE); // close on Escape
    }

    if ($key === GLFW_KEY_S && $action === GLFW_PRESS && ($mods & GLFW_MOD_CONTROL)) {
        $this->save(); // Ctrl+S
    }
});

The $action is GLFW_PRESS, GLFW_RELEASE, or GLFW_REPEAT. Checking for GLFW_PRESS means your handler fires once when the key goes down, not every frame it is held.

Physical keys are not text

glfwGetKey and the key callback deal with physical keys, not characters. GLFW_KEY_A is the position of the A key, not the letter the user typed (that depends on their layout, Shift, and dead keys). Never build text input by concatenating key names. For text, use the character callback below.

Receiving typed text

When you want the actual characters a user types, into a search box or a chat line, listen for glfwSetCharCallback. It fires once per Unicode codepoint and already accounts for layout, Shift, and dead keys. Your closure receives a single ($codepoint), an integer you turn into a string with mb_chr:

$typed = '';

glfwSetCharCallback($window, function ($codepoint) use (&$typed) {
    $typed .= mb_chr($codepoint); // appends the real character, "A", "ä", "文", ...
});

You need the mbstring extension

mb_chr lives in PHP's mbstring extension, so make sure it is loaded before you decode codepoints. The bundled example guards this with extension_loaded('mbstring') at startup.

If you also need to know which modifiers were held while the character was produced, use glfwSetCharModsCallback instead, whose closure receives ($codepoint, $mods). For ordinary text entry, the plain character callback is what you want.

Mouse buttons

Just like keys, mouse buttons can be polled or delivered by callback. To ask about a button right now, use glfwGetMouseButton:

if (glfwGetMouseButton($window, GLFW_MOUSE_BUTTON_LEFT) === GLFW_PRESS) {
    $ship->fire();
}

For click events, register glfwSetMouseButtonCallback. The closure receives ($button, $action, $mods):

glfwSetMouseButtonCallback($window, function ($button, $action, $mods) {
    if ($button === GLFW_MOUSE_BUTTON_RIGHT && $action === GLFW_PRESS) {
        echo "context menu" . PHP_EOL;
    }
});

The common buttons have friendly names, GLFW_MOUSE_BUTTON_LEFT, GLFW_MOUSE_BUTTON_RIGHT, and GLFW_MOUSE_BUTTON_MIDDLE, and the rest are numbered GLFW_MOUSE_BUTTON_1 through GLFW_MOUSE_BUTTON_8 for mice with extra buttons.

Where the cursor is

To read the cursor position, call glfwGetCursorPos. It does not return the coordinates, it writes them into two variables you pass by reference:

$mouseX = 0.0;
$mouseY = 0.0;
glfwGetCursorPos($window, $mouseX, $mouseY);
// $mouseX and $mouseY now hold the position, in screen coordinates,
// measured from the top-left of the window's content area

If you would rather be notified only when the cursor moves, use glfwSetCursorPosCallback, whose closure receives ($xpos, $ypos). And to know when the cursor crosses your window's edge, glfwSetCursorEnterCallback gives you ($entered), which is truthy on the way in and falsy on the way out:

glfwSetCursorEnterCallback($window, function ($entered) {
    echo $entered ? "cursor entered" . PHP_EOL : "cursor left" . PHP_EOL;
});

The scroll wheel

Scrolling, whether from a wheel or a trackpad gesture, arrives through glfwSetScrollCallback. The closure receives ($xoffset, $yoffset), and most of the time you care about $yoffset:

glfwSetScrollCallback($window, function ($xoffset, $yoffset) use (&$cameraZoom) {
    $cameraZoom -= $yoffset * 0.1; // scroll up to zoom in
});

Cursor modes: hiding and grabbing the cursor

For a first-person camera you do not want a visible cursor drifting to the edge of the screen and stopping. You want to capture it, so the mouse can turn the camera forever in any direction. That is what input modes are for. Set them with glfwSetInputMode and the GLFW_CURSOR mode:

// hide and lock the cursor to the window, virtual movement is unlimited
glfwSetInputMode($window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);

The three cursor modes are:

Value Effect
GLFW_CURSOR_NORMAL the ordinary visible cursor (the default)
GLFW_CURSOR_HIDDEN invisible while over the window, but not locked
GLFW_CURSOR_DISABLED hidden and locked to the window, movement is unbounded, ideal for 3D cameras

With the cursor disabled, read motion through the cursor-position callback as usual, and treat the deltas between frames as how far to rotate the camera. To hand control back to the user, for example when they open a menu, set the mode back to GLFW_CURSOR_NORMAL.

You can query the current mode at any time with glfwGetInputMode.

Raw mouse motion

When the cursor is disabled, you usually want raw, unaccelerated motion so the camera is not affected by the operating system's mouse acceleration curve. Enable it with the GLFW_RAW_MOUSE_MOTION mode, but only after checking that the platform supports it:

if (glfwRawMouseMotionSupported()) {
    glfwSetInputMode($window, GLFW_RAW_MOUSE_MOTION, GLFW_TRUE);
}

Check support first

Raw motion is only available while the cursor is disabled, and enabling it on a platform that does not support it raises a GLFW_PLATFORM_ERROR. Always guard the call with glfwRawMouseMotionSupported. Support does not change while your program runs, so a single check at startup is enough.

Sticky input

If your loop runs slowly and you worry about missing a very quick tap, turn on sticky mode. With GLFW_STICKY_KEYS (or GLFW_STICKY_MOUSE_BUTTONS), a press is remembered until the next time you poll it, so a key that was pressed and released between two frames still reads as GLFW_PRESS once:

glfwSetInputMode($window, GLFW_STICKY_KEYS, GLFW_TRUE);

Cursor shapes

You can swap the cursor image to hint at what is interactive: a hand over a link, an I-beam over text, a crosshair over a target. Create one of the standard shapes with glfwCreateStandardCursor and apply it with glfwSetCursor:

$hand = glfwCreateStandardCursor(GLFW_HAND_CURSOR);
glfwSetCursor($window, $hand); // show the hand cursor

// ... later, back to the default arrow ...
glfwSetCursor($window, null);

// when you are done with it entirely
glfwDestroyCursor($hand);

The available shapes are GLFW_ARROW_CURSOR, GLFW_IBEAM_CURSOR, GLFW_CROSSHAIR_CURSOR, GLFW_HAND_CURSOR, GLFW_HRESIZE_CURSOR, and GLFW_VRESIZE_CURSOR. Passing null to glfwSetCursor restores the default arrow, and glfwDestroyCursor frees a cursor you no longer need.

Note

PHP-GLFW ships the standard system cursors only. Building a cursor from your own image (glfwCreateCursor in the C API) is not currently exposed.

The clipboard

Copy and paste go through a pair of functions. Put text on the system clipboard with glfwSetClipboardString, and read it back with glfwGetClipboardString:

// copy
glfwSetClipboardString($window, "shared from my app");

// paste
$fromClipboard = glfwGetClipboardString($window);

Both work with UTF-8 strings. glfwGetClipboardString returns null when the clipboard is empty or holds something that is not text, so check before using the result.

Full API Reference

For the exhaustive list of every function, argument, and constant, browse the generated GLFW function reference. The key input entry points are glfwGetKey, glfwSetKeyCallback, glfwSetCharCallback, glfwGetCursorPos, glfwSetInputMode, and glfwSetScrollCallback.