Native GLFW platform reference
Cheryl::NativeGLFW supplies GLFW-backed display/windows and optional Gainput-backed
input. It implements the engine’s display and input contracts from one platform
owner, which also manages GLFW lifetime and native callback diagnostics.
Selection and composition
The root selects this owner with CHERYL_BUILD_NATIVE_GLFW=ON. Set
CHERYL_BUILD_OPENGL=OFF to use the platform module without Cheryl’s graphics
backend; UI selection is independent. The library’s build target is
module_native_glfw, with output name cheryl-module-native-glfw. Applications link:
target_link_libraries(game PRIVATE Cheryl::NativeGLFW)
Engine usage settings propagate through the target. GLFW, Gainput and selected HID implementations are privately owned by this module; consumer headers expose Cheryl-owned types and binding/query operations. Selection options are:
Option |
Default |
Effect |
|---|---|---|
|
|
Includes Gainput-backed input and its Linux X11 requirement. |
|
|
Configures an owned GLFW target without X11/Wayland. Disable native input as well to omit Gainput/X11. |
|
|
Selects this owner’s implementation checks. |
From the repository root, a standalone build can bootstrap an explicit Engine checkout without selecting other integration owners:
(
set -e
cd "$(git rev-parse --show-toplevel)"
cmake -S projects/modules/platform/native-glfw -B build/native-module -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DCHERYL_REPOSITORY_ROOT="$PWD"
)
(
set -e
cd "$(git rev-parse --show-toplevel)"
cmake --build build/native-module --target module_native_glfw --parallel
)
On Linux, bundled GLFW enables both X11 and Wayland; use
GLFW_BUILD_WAYLAND=OFF for X11 only. See the
build guide for system requirements and
Gainput’s optional HID fetch.
Use with an application
For a GLFW/OpenGL game, use the standard Startup path
to assemble the compatible display, input, presentation, renderer and resources.
Link Cheryl::Cheryl for the configured assembly, or select granular targets as
shown in Source and SDK integration.
The runtime initializes input before the game’s init_platform() and detaches it while
its display-owned window is still live.
Bind controls during platform-owned initialization using the binding task. Reusable runtime consumers can attach/remove action contexts through queued requests; direct binding edits still belong to the polling owner. During simulation, use the tick’s action state and copied logical/framebuffer dimensions instead of polling or querying a live window. For a platform-only application, link Native GLFW and use its display/window interfaces directly; graphics/presentation setup belongs to the selected graphics implementation.
Choose a controller path
Configuration |
Current usable scope |
|---|---|
Linux with |
The joydev path reads |
Direct HID selection |
Integration is unfinished. Enabling the CMake option alone does not establish active polling, correct report identity or retained pad state. |
Windows or macOS controllers |
Backend-specific gaps remain and the Linux observation does not accept those platforms. |
Linux controller mapping explains supported reports and fixed device selection; controller backend limits retains the source diagnosis for extending the integration. To investigate a missing press, start with controller diagnostics.
Engine contracts
Engine contract |
Implementation |
Responsibility |
|---|---|---|
Monitor snapshots, owned windows and active-window selection. |
||
Window sizes/modes, resizing, cursor visibility and deferred callback failures. |
||
Device bindings and published State/Events/Text polls with capture and focus routing. Selected by |
The graphics owner selects its window client API and supplies rendering and presentation. GLFW/OpenGL context selection, buffer swapping and the native graphics factory live in the OpenGL module. A different graphics implementation can use the platform window/input facilities with its own API-specific setup.
The display/window contract
defines platform affinity, borrowed lifetimes, construction-time monitor snapshots,
size observations and scale/cursor capability limits. Framebuffer changes publish
the typed CE::Platform::window_resized_event channel alongside the legacy named notification;
the contract defines their ordering and failure boundary.
With native input enabled, the owner reuses a supplied gainput::gainput,
gainputstatic or gainput target, in that order, or adds the selected
extern/gainput source. Supplied compiled implementations must be compatible
static inputs owned by Native GLFW; they do not propagate another implementation
copy or upstream public headers to application consumers.
Supplied dependency targets keep their host’s settings. Owned source composition
suppresses Gainput samples/tests locally, including cached ON choices, without
rewriting the host cache. The selected fork’s GAINPUT_ENABLE_HID setting remains
available; its HID support brings hidapi and the platform’s HID development libraries.
GLFW and enabled HIDAPI use shared runtimes independently of Cheryl’s own linkage.
HIDAPI selection reuses a supplied target, a package or Linux pkg-config before
fetching the pinned source. Linux defaults to HID disabled; when enabled, its
default backend is hidraw. Windows forces HID support and needs a shared HIDAPI
DLL in addition to the OS HID APIs.
Input lifetime and mapping
Gainput’s HID state has one initialized native adapter as its process-wide owner.
The adapter claims ownership before Init, creates devices on its first window
attachment, and calls Exit at destruction. Another native adapter’s initialization
fails explicitly while that owner exists, including while its callbacks are detached.
Failed ownership acquisition leaves the existing adapter intact; an exception from
Init releases the claim. Native manager state is private; applications must
not run an independently initialized Gainput implementation alongside this adapter.
Application bindings accept CE::Input::KeyboardKey, MouseButton and
GamepadControl directly, paired with this adapter’s device IDs. Pointer axes
and wheel-button pulses use CE::Input::MouseControl. Include
<cheryl/core/controls/button-codes.h>; no public GLFW translation helper is
required. The control guide
lists the families and binding examples.
Private native translation tables are generated from the Engine enums and pinned
GLFW/Gainput headers. After changing those declarations, run
python3 tools/input/generate_controls.py from the checkout root and commit its
output; --check verifies freshness and runs in CI before module builds.
The generator keeps semantic naming
exceptions separate from mechanically derived mappings. Consumers use the
committed implementation and need neither the generator nor upstream headers.
InputSystem::device() returns a copied
device snapshot and backend_time_ms() observes the backend clock. These APIs
replace public Gainput inheritance, manager members and SDK object access.
The inherited gamepad_availability() snapshot and
CE::Input::gamepad_availability_changed_event observe the single convenience
slot after complete poll publication. Runtime selects its Context bus; standalone
hosts call input.publish_events_to(events.publisher()) before polling. See
gamepad subscription and seed
for identity, closure and delivery rules.
Window/context-current, procedure lookup, swap interval and buffer swapping are exported native operations used by OpenGL, so OpenGL uses Native GLFW’s integration with the shared GLFW runtime.
When HID is compiled in, the selected Gainput fork discards the HID backend’s
initialization return code.
Ownership rollback covers exceptions; successful Init alone does not establish
HID device/notification readiness. Native HID acceptance verifies that behavior
independently of owner-policy checks.
Windows initialization receives the GLFW window’s native HWND. Reattachment requires
that same native window, which remains live until the adapter is destroyed; recreate
the adapter before replacing the notification window. Other platforms initialize
the HID backend without a window handle and can reattach to a different live GLFW
window. Detachment clears window state while retaining devices and ownership.
Each Update receives elapsed steady-clock seconds between attached polls; time
spent detached does not enter that interval.
Reusable controller automation with HID disabled is in the Desktop and controller checks; Windows acceptance is shelved in the platform plan.
The private controller listener uses Gainput’s five-argument
OnDeviceButtonFloat callback. It normalizes the native control ID and forwards
newValue as the current Cheryl axis state; the elapsed-time argument does not
scale that value. Key/mouse callbacks feed Cheryl bindings directly in their
shared observation order, including F20–F25. Those devices also retain a
private Gainput state mirror without replaying its notifications into bindings.
Target selection and standalone paths are in the module guide.
Linux controller mapping
The selected Gainput Linux joystick backend opens /dev/input/js0 for the first
pad. Existing named PS3 and Xbox 360 dialects retain their mappings. Other
controllers use JSIOCGBTNMAP and JSIOCGAXMAP to translate driver event slots into
gamepad controls following the
Linux gamepad specification.
DualSense Cross maps from BTN_SOUTH through Gainput’s PadButtonA to
CE::Input::GamepadControl::A; right-stick and trigger axes
use their kernel codes, and hat axes produce directional button state. Unsupported
codes are ignored. If mapping ioctls fail, the previous raw-axis fallback remains;
button support is unavailable for an unrecognized dialect.
Translated reports update the created pad’s retained state and emit its actual
logical device ID. Disconnect clears held buttons/axes and closes the descriptor;
reopening the joystick reloads its mappings. This path requires a readable joydev
node and keeps the existing fixed joystick-index selection. It does not scan for a
particular physical controller or identify a reconnected device across node changes.
Use this path with GAINPUT_ENABLE_HID=OFF while the HID integration below
remains unresolved.
The demo binds Cross/A to the HUD press counter. Its other gamepad controls have no gameplay bindings; their traces can verify decoding without moving the camera. Physical controller acceptance covers a Bluetooth DualSense on Linux/X11 with HID disabled; USB, HID and Windows controller behavior remain separate coverage. The reusable joystick controller checks exercise reports, held input, reconnection and startup attachment in both runtimes.
Controller backend limits
The pinned Gainput fork’s CMake HID selection includes decoder sources and hidapi,
but does not supply the compiler definition GAINPUT_ENABLE_HID that guards the
manager’s HID initialization and polling. The existing Linux native build’s compile
rules omit that definition, so those runtime calls are compiled out. Enabling the
CMake option alone does not establish an active HID path.
Both PS4 and PS5 HID parsers emit listener deltas under Gainput’s fixed
CONTROLLER_ID (4). This is a logical Gainput device ID, not a DualSense hardware
identifier. The native adapter’s normal keyboard/mouse/pad creation assigns the pad
ID 2. The parsers also do not update that pad’s retained state or establish its
availability through the HID connection. These remain integration defects when the
HID runtime is enabled.
Cheryl uses the IDs returned by Gainput’s device creation API and queries the pad’s availability/current state after each update to reconcile held controls and disconnection. Repairing the feature wiring, report identity and retained pad state belongs in the Gainput fork; Cheryl’s acceptance must verify that integration. Controller acceptance also requires disconnect clearing and one selected report source for each controller. HID device visibility and access also require observation in the user’s desktop session; successful Linux joystick reads do not establish HID access. The Gainput handoff owns the deferred backend correction and adaptive-trigger sequence. The HID integration tracks Cheryl’s remaining lifetime and observation requirements.
HID capability and platform scope
The shared HID layer whitelists DualShock 4, DualShock 4 Slim and DualSense; it does not provide a generic decoder for every HID controller. Its PS4/PS5 parsers contain USB and Bluetooth report decoding, including ordinary controls, touch contacts and motion sensors. Source presence does not establish usable Gainput pad support:
Capability |
Current integration limit |
|---|---|
Buttons, sticks and triggers |
Require the report identity, retained state and availability bridge described above. |
Touch and motion |
Use the same unfinished bridge. Both parsers send each contact’s X and Y to the same axis ID; touch routing needs correction before acceptance. |
Conventional rumble and lights |
HID output packet code exists, but the normal pad API’s HID forwarding is commented out. Device selection and rumble duration handling need correction. |
Battery and adaptive triggers |
DualSense battery information remains private parser data; adaptive-trigger output is an unimplemented disabled stub. Neither is an exposed feature contract. |
The platform fallbacks also depend on this integration. Windows DirectInput removes whitelisted Sony controllers on the assumption that HID handles them. The macOS native pad implementation maps selected Xbox controllers and leaves PlayStation controllers to HID, while the Apple CMake branch omits the shared HID runtime and parser sources. These are source-level gaps; Windows and macOS controller behavior requires platform acceptance. The Linux joystick correction does not establish coverage on either platform.
For direct Bluetooth reports on Linux, select hidapi’s hidraw backend; its libusb backend supports USB only, as described in the hidapi documentation. The working joystick route already uses kernel-decoded Bluetooth input. Direct HID is also not the only route to advanced Linux features: the Sony kernel driver exposes motion/touch input, conventional rumble, battery and light controls through Linux interfaces that this joystick adapter does not consume.
Cheryl’s portable input interface currently exposes control input without rumble, light or battery APIs. Select the desired feature scope before extending that consumer contract; repairing Gainput alone does not expose those features to games.
Controller diagnostics
InputSystem::set_gamepad_diagnostics(true) opts in on the platform owner before
startup or between polls. TRACE must be compiled and admitted by the OS-platform
logger and a destination. The demo’s --input-diagnostics configures its file/logger
gates before creating the context and enables the adapter’s trace. Normal mapping,
device IDs and state reconciliation are unchanged.
Records go to logs/os-platform.log, relative to the launch directory:
Operation |
Evidence |
|---|---|
|
DEBUG entry/return around |
|
DEBUG assigned pad ID, the public header’s HID report ID when available, and trace selection. |
|
Opt-in TRACE callback device/control IDs and old/new values for pads or unresolved device IDs; known keyboard/mouse devices are omitted. |
|
Opt-in TRACE pad values that change during the adapter’s full-state reconciliation. |
|
Opt-in TRACE availability, device state, A-button validity and sampled value, at most once per second. |
Callback records use the mapper’s diagnostic domain; setup/sample records use the
adapter’s domain. Match device IDs across these stages. An unresolved callback ID
matching hid_report_device identifies a routing mismatch candidate; it does not
identify a physical controller or independently establish HID report delivery.
Axis callbacks alone do not establish button delivery. Cross/A needs a button
delta and sampled transition for PadButtonA on the assigned pad, followed by the
demo counter. An available pad or valid button ID alone does not establish decoding.
Inspect the compiler command for Gainput’s own GainputInputManager.cpp, not the
adapter’s compile definitions, when checking the HID guard. A returned initialization
call or continuing poll heartbeat cannot establish enumeration, device-open success
or notification registration. Those observations still need hooks in Gainput.
The Desktop and controller checks
supplies build inspection and Bluetooth capture procedures; HID observations
remain blocked in the testing queue.
Checks
Target |
Output / kind |
Selection / coverage |
|---|---|---|
|
|
|
|
|
Linux GNU/Clang, owned static Gainput, HID disabled: synthetic kernel maps, button actions, sticks/hats, disconnect/reconnect and legacy dialects. |
|
|
Ordinary owner GoogleTests; |
|
|
|
|
Object library |
Consumer’s first-include header probes; built with the consumer. |
The joystick runner uses private syscall wrapping and stays separate from owner aggregates. It requires no physical joystick/display and does not accept HID startup, device permissions or physical reconnect behavior.
Native graphics runtime checks belong to the OpenGL owner’s opt-in acceptance suite. Use the composition procedures when changing module boundaries.