Haiku Keyboard Events: Separating Key Codes from Text Input
Trace Haiku keyboard input from keymap lookup to BView::KeyDown, and keep unmapped keys, text bytes, modifiers, and input methods distinct.
A key press can have more than one useful meaning. An editor may need the text the user is entering, while a game or shortcut handler needs the key’s identity even when the current layout maps it to no character. Haiku exposes both views of keyboard input. Treating a text byte as a hardware key code - or assuming every key press is one printable character - creates layout, modifier, and international-input bugs.
This article follows the application-facing boundary: the BView::KeyDown() hook, the keyboard BMessage forms, and the keymap lookup that connects them. It does not explain how to write an input-server device add-on or implement an input method; those are separate system components with their own lifecycles.
A view receives text bytes, not a portable key identifier
Haiku declares BView::KeyDown(const char* bytes, int32 numBytes) and the matching KeyUp() hook in the public Interface Kit header. The older Be Book explains the intended hook argument: the byte array represents the character produced with the active modifier state, and numBytes tells the receiver how much data is present. The view is therefore a useful place to handle text-oriented keys, but its callback is not a universal physical-key API.
Use the count supplied by Haiku. Do not assume that the callback contains exactly one byte, and do not keep the pointer after the callback returns. If a view needs to retain the entered text, copy exactly numBytes bytes into owned storage. A single-byte check is appropriate for an ASCII control key such as Escape; it is not a general text-decoding strategy.
void
EditorView::KeyDown(const char* bytes, int32 numBytes)
{
if (numBytes <= 0)
return;
if (numBytes == 1 && bytes[0] == B_ESCAPE) {
CancelTransientMode();
return;
}
// This view is a text destination: consume the supplied byte span,
// rather than treating bytes[0] as a hardware key number.
InsertText(bytes, numBytes);
}
InsertText() above stands for the application’s own insertion operation. A custom control should normally preserve its editing invariants - selection replacement, undo grouping, and validation - instead of appending blindly. If the view is a text control with built-in editing behavior, call its base-class implementation for input the subclass does not intercept.
A keyboard message also carries key identity
The public AppDefs.h header declares B_KEY_DOWN, B_KEY_UP, and the corresponding B_UNMAPPED_KEY_DOWN and B_UNMAPPED_KEY_UP message codes. Haiku’s current keyboard input-device implementation shows what distinguishes them: after asking the active keymap for characters, it selects the mapped or unmapped message form and stores the key code and modifier state. The byte, bytes, and repeat fields are conditional on mapped bytes; raw_char is added independently when the unmodified keymap lookup yields a byte.
In that implementation, the message’s key field is the key code used for keymap lookup. The bytes field carries the mapped byte string; byte appears once for each byte, while raw_char is derived from the unmodified keymap result when one is available. A repeat count is added for repeated key-down messages. These are implementation details observed in the current source, not a guarantee that every historical BeOS-compatible application server emits every field identically.
For a physical-key command, inspect the key code rather than the text bytes. For a text editor, consume text through the view’s hook or the appropriate input-method path. A message handler that needs both should preserve the distinction explicitly:
void
ShortcutHandler::MessageReceived(BMessage* message)
{
switch (message->what) {
case B_KEY_DOWN:
case B_UNMAPPED_KEY_DOWN: {
int32 keyCode;
if (message->FindInt32("key", &keyCode) == B_OK)
HandleKeyIdentity(keyCode);
break;
}
default:
BHandler::MessageReceived(message);
}
}
The handler uses the public message constants and checks the field lookup result. It does not infer that an unmapped event is an error: a function key, an unusual device key, or a keymap state that produces no text may still be meaningful to the application. Conversely, a mapped key message should not be treated as a request to insert every field as text; normal focus routing and text-control behavior still matter.
Keymaps explain why the two values change differently
Haiku’s public key definitions and key_map structure describe modifier keys, per-key mapping tables, and dead-key data. The input-server keyboard implementation consults that map to obtain characters for a key code. A layout change can therefore alter the text bytes associated with a key while the key code used to identify the key remains the same. This is why shortcut matching and text insertion should not be built on one shared integer comparison.
Dead keys make the separation visible. A dead key can begin a composition instead of immediately producing a completed character. The current keyboard implementation tracks that state and can enqueue inline input-method start, change, and stop events when the combination completes. Haiku also defines B_INPUT_METHOD_EVENT as a separate message code. Applications that support composed or language-method input should follow the text control and input-method contract; manually turning each physical key into a character can bypass that composition behavior.
Do not interpret “key code” as a USB HID usage or a cross-platform scan code without a documented conversion. Haiku’s InterfaceDefs.h describes the native key and mapping structures; its numeric values belong to this API. If an application saves shortcuts, define whether it stores a Haiku key code, a produced character, or a portable key abstraction, then version and migrate that choice deliberately.
Test the boundaries that reveal mistaken assumptions
Test with at least two keymaps and keep the physical keyboard fixed. A letter shortcut should remain attached to the intended key identity if that is the design, while text entry should follow the selected layout. Add Shift and Option/Command combinations, a dead-key sequence, a key that produces no mapped bytes, and a repeated key-down. Confirm the app does not insert a shortcut’s raw key code as text or lose valid text because it assumed one byte.
Also test focus. A key delivered to a text view, a window shortcut handler, and an inactive view are not equivalent inputs. Let Haiku’s normal dispatch route the event to the focused target; avoid polling global key state as a replacement for text delivery. For diagnostic logging, record the message code, key code, modifier mask, repeat field when present, and the exact byte count. Redact entered text if logs could contain user data.
The current Haiku source review supports the message-field observations above, while the Be Book documents the BView callback model. Neither establishes that every release, input add-on, or third-party input method behaves identically. Build and test against the headers and runtime you ship. Keeping key identity, text bytes, and composition as separate concepts makes the code easier to port and gives layout changes a predictable result.
Related:
- How to Configure and Test a Non-US Keymap on Haiku
- Haiku’s Input Server: Device Add-ons, Event Filters, and Input Methods
Sources:
- Haiku
BViewkeyboard hook declarations (raw upstream source) - Haiku application keyboard message constants (raw upstream source)
- Haiku key definitions and key-map structures (raw upstream source)
- Haiku keyboard input-device message construction (raw upstream source)
- Be Book: Programming the Be Operating System, “Views, Windows, and Messages”