Haiku's Input Server: Device Add-ons, Event Filters, and Input Methods
Trace Haiku input from device nodes through input_server add-ons, message filters, and text methods, with lifecycle and event-flow guidance.
Haiku separates low-level hardware support from the user-space Input Server’s interpretation and routing of input. A kernel driver can publish a device node without deciding what a keyboard event means to applications. The Input Server loads add-ons that discover or manage logical input devices, translates device activity into the message vocabulary used by the desktop, applies event filters, and coordinates input methods. That division gives hardware backends, desktop policy, and text composition different extension points.
This is not the same layer as a kernel device driver and it is not the same API as BMessageFilter attached to one application’s BLooper. Input Server add-ons are system-wide extensions. The current public headers and in-tree implementations are the most useful technical references: Haiku’s Input.dox still labels much of the general Input API undocumented, so treat concrete behavior as tied to the source revision you build against rather than assuming a complete frozen contract.
Locate the boundaries in the event path
A useful operational model has four stages:
- A bus or class driver exposes a hardware endpoint, commonly under
/dev/input/.... - A device add-on registers a logical keyboard, pointing device, or another supported device type. It starts or stops a reader when requested and translates low-level reports into Haiku input messages.
- Input Server filters inspect the event stream and can let an event continue, suppress it, or replace it with events they create.
- The server routes accepted events to the desktop/application path. A separate input-method add-on participates in text composition and presents its own activation/name/icon/menu behavior.
The APIs deliberately distinguish device discovery from device events. A low-level device driver should not pretend that it is a text input method, and a filter should not open hardware nodes to reimplement the device add-on. When debugging, capture evidence at each boundary: does the node exist, did the add-on load, did it register the logical device, did it start, did it enqueue messages, and did the desktop receive the resulting event?
The Haiku source tree organizes add-ons under the logical directories input_server/devices, input_server/filters, and input_server/methods. The server’s add-on manager loads a shared image, looks up a type-specific factory symbol, constructs the object, and calls InitCheck(). The current factory names are instantiate_input_device(), instantiate_input_filter(), and instantiate_input_method(). An add-on that cannot initialize should return its error from InitCheck(); the manager discards that object instead of registering a partially initialized extension.
extern "C" BInputServerDevice*
instantiate_input_device()
{
return new(std::nothrow) MyInputDevice();
}
This is the factory shape used by Haiku’s in-tree keyboard and mouse add-ons. The concrete class must derive from the corresponding public base class and include the relevant header. Do not assume that exporting an arbitrary function with a similar name is enough: the add-on directory determines which factory name and base class the manager expects.
Register a logical device and respect its lifecycle
input_device_ref contains a name, an input_device_type, and a cookie. The type currently distinguishes pointing devices and keyboards. The cookie is opaque to Input Server; an add-on can use it to associate a registered logical device with its own state. Names must be unique enough for the user-visible device roster and should remain valid for the lifetime required by the registration.
An add-on typically prepares a null-terminated array of pointers to its input_device_ref records and passes it to RegisterDevices(). The server records the devices and notifies clients watching the roster. Its Start(device, cookie) and Stop(device, cookie) hooks are per logical device: start the worker, open the relevant endpoint, and establish state only when a device is started; stop readers and release resources when it stops. Control() receives setting-change notifications and device-specific control codes, while SystemShuttingDown() offers a shutdown boundary. Make repeated start/stop and partial initialization safe; a failed open must not leave a thread marked active or a half-registered device.
The source implementation calls the add-on’s lifecycle methods as it handles device state. The in-tree keyboard and mouse add-ons demonstrate a worker thread reading a device endpoint and posting resulting events. Keep blocking reads away from the Input Server’s message-processing path. A reader should have an explicit cancellation strategy, check every open/read/ioctl result, and make Stop() wait for its own work to finish before freeing state referenced by that worker.
status_t MyInputDevice::InitCheck()
{
input_device_ref* devices[] = { &fDevice, NULL };
return RegisterDevices(devices);
}
The example shows the registration shape only. In a real implementation, construct and validate fDevice before registration, and unwind any resource allocated by the constructor when initialization fails. UnregisterDevices() should be called when a logical device is permanently removed; the add-on’s destructor also unregisters devices owned by that add-on in the current implementation.
Monitor hardware nodes without confusing them with roster entries
StartMonitoringDevice() and StopMonitoringDevice() let a device add-on watch a path in the /dev tree. The current server implementation normalizes a relative name by prefixing /dev/ and installs recursive file monitoring for that path. When a matching node is created or removed, the manager calls the add-on’s Control() hook with an input-device-added or input-device-removed notification. A device add-on can then open the endpoint, create or remove an input_device_ref, and keep its inventory synchronized.
This monitoring path is not a replacement for the kernel device manager. It watches a user-space path; it does not bind a kernel driver, guarantee that a file is a usable device, or keep an already opened file descriptor valid after physical removal. Validate the endpoint by opening it and checking the operations the driver actually supports. Reconcile after a notification because another process can remove a node between notification delivery and your open.
Be explicit about ownership and shutdown. Track each opened descriptor, worker thread, and registered logical device together. On removal, stop new reads, cancel or unblock the current read using the supported device behavior, join the worker, unregister the logical device, and only then free the cookie/state. If an endpoint reappears with the same path, treat it as a new open and new lifecycle rather than assuming it is the same kernel object.
Convert raw reports into the established message vocabulary
EnqueueMessage() submits an event to Input Server. In-tree add-ons create typed BMessage objects such as key and mouse events, populate the fields expected by the corresponding Haiku interface, and submit them. The keyboard add-on checks the enqueue status and deletes the message when submission fails; it does not continue using the message after successful submission. Follow the exact event schema in the public headers and current in-tree producer/consumer examples. A message code alone is not a complete event contract: fields such as key code, modifiers, buttons, coordinates, or timestamps must match the event type.
Do not enqueue one event for every low-level packet without considering semantics. A keyboard transition should preserve key-down/key-up ordering and modifier state. A pointing device should preserve button transitions and coordinate deltas appropriately. If the driver reports a reset, lost packet, or unsupported capability, surface that state through a controlled recovery path rather than fabricating a normal input event. Timestamp and ordering assumptions matter to double-click behavior, cursor movement, and applications that track press/release pairs.
Treat event submission as fallible. Handle allocation and enqueue errors; avoid logging every high-rate movement at a synchronous console destination. Make logging bounded and useful: device name, operation, error, and enough context to distinguish a dropped packet from a stalled reader. Test a key press and release, modifier combinations, repeat behavior, unplug during input, and replug after the add-on has fully stopped the old endpoint.
Keep global filters bounded and predictable
BInputServerFilter::Filter(BMessage*, BList*) is a system-wide event hook. The filter result includes B_DISPATCH_MESSAGE and B_SKIP_MESSAGE. The current server code applies filters to queued event messages; a filter can suppress the current message or, when it returns a dispatch result, provide replacement/additional messages through the output list. This is a different lifecycle and routing scope from a BMessageFilter attached to a single application’s handler.
Use a global filter only for policy that genuinely belongs at the system input layer. Keep the callback bounded and deterministic. It should not block on network, disk, or UI work, and it should not assume every event contains optional fields. If a filter changes an event, preserve its meaning for downstream code: key-down and key-up must remain paired, coordinates must use the expected screen or view convention, and timestamps must not be arbitrarily reordered. A filter that consumes an event can affect every application, so make its activation and failure behavior observable and reversible.
The filter base class exposes InitCheck() and GetScreenRegion() in addition to Filter(). The current input-server implementation owns registered add-on objects and unloads them as add-ons are disabled or the server shuts down. Avoid retaining a pointer to a message beyond the callback unless you make an independent copy; the event list is managed by the server. For first experiments, build a filter that observes and counts a narrowly selected event before attempting to rewrite or suppress it.
Distinguish text input methods from physical devices
BInputServerMethod is a separate extension path for input methods. It inherits the filter base but adds activation, name, icon, and menu operations. A method can enqueue input-method messages, while the Input Server’s method UI can activate it and display its current identity. The input_method_op values in Input.h include started, stopped, changed, and location-request events. These indicate composition state; they are not keyboard-driver notifications.
Haiku’s in-tree keyboard path illustrates the handoff: it can send B_INPUT_METHOD_EVENT messages with a be:opcode and composition data when a dead key or method interaction is in progress. Consumers should inspect the opcode and handle a stop/change transition even if a key sequence was interrupted. Input methods should not assume a method remains active indefinitely or that the next keystroke belongs to a previous composition session.
Because device, filter, and method add-ons all share the Input Server process, keep resource ownership and failure behavior explicit. A method that cannot allocate its menu or start a helper should report failure cleanly, not leave a stale active state in the UI. A device removal should end any composition or event state that depended on that physical device.
Diagnose and accept an add-on in layers
Start by verifying the add-on binary exists in the correct category and matches the running Haiku architecture. Confirm that its exported factory has the expected exact spelling and C linkage. Check Input Server logs for image-load, factory-lookup, or InitCheck() failures. Next verify registered logical devices through get_input_devices() or find_input_device(), then start the device and confirm its worker opens the expected /dev/input endpoint.
Test filters and methods in isolation. A device add-on that works without a filter but fails with one suggests message transformation or ordering; a method that appears but never receives composition messages suggests activation or protocol handling. Confirm add-on disable/unload behavior and restart the server or system in a development environment after each change. Do not hot-remove the only working input device from a machine needed for interactive recovery.
An acceptance record should include the Haiku revision, architecture, add-on filename, factory symbol, input endpoint, registered logical device name/type, start/stop behavior, observed event sequence, and tested remove/re-add behavior. Also document which interface details come from current source because the public prose reference remains incomplete. That gives maintainers enough evidence to distinguish an implementation regression from an unsupported device or a mismatched API assumption.
Related:
- Device Drivers and Hardware Support in Haiku
- Haiku Teams, Threads, Ports, and the Kernel Object Model
Sources: