Haiku Control-Look Add-Ons: Runtime Loading, Fallbacks, and Safe Testing
Trace how Haiku resolves a control-look add-on, where its factory runs, and how to test custom appearance code without assuming ABI stability.
A Haiku control look is the Interface Kit’s rendering and sizing policy for standard controls. It is separate from a window decorator, which draws window frames, and from a control’s behavior: a BButton still owns its label, value, invocation message, and activation rules. The distinction matters when diagnosing a control that looks wrong but still sends the right command, or when deciding whether an application should depend on the control-look API at all.
The short answer for ordinary application code is to use standard Interface Kit controls and let them ask the active look to draw themselves. The current BControlLook header explicitly calls its API experimental and warns that it may change. Its presence in an installed header and its unqualified using alias do not make it a stable compatibility promise.
What the look controls
BControlLook supplies drawing operations for button frames and backgrounds, labels, menus, scrollbars, text controls, and other common elements. It also reports layout metrics such as frame/background insets and default label or item spacing. A replacement therefore changes more than color and bevels: it can affect a control’s preferred size and the space its parent layout must reserve.
The standard BButton::Draw() path shows the boundary clearly. It asks be_control_look to compute state flags, adds button-specific flags such as default-button and hover state, then calls the look to draw the frame, background, and label. The button’s own interaction path remains separate. A custom look should preserve the visual distinctions represented by flags such as focused, clicked, activated, disabled, default, and invalid; it should not attempt to replace application validation or command handling.
This is also why a custom control look needs more than a screenshot review. Its insets and spacing must agree with what the controls report to layouts, while drawing must remain correct for the supplied update region and for different control states. A look that paints a pleasing enabled button but erases focus feedback or clips a translated label is not a compatible replacement.
How Haiku selects the implementation
The current Interface Kit initializer asks app_server for the configured control-look path. If it receives a non-empty path, it loads that add-on, looks up the C symbol instantiate_control_look, and calls the factory with the loaded image ID. If no path is configured, loading or symbol lookup fails, or the factory returns NULL, be_control_look remains unset and the initializer creates Haiku’s built-in HaikuControlLook implementation instead. During Interface Kit teardown, it deletes the object before unloading the add-on image.
This path is easy to misdescribe. The client asks app_server for the selected path, but _init_interface_kit_() calls load_add_on() in src/kits/interface/InterfaceDefs.cpp; BButton::Draw() then calls the selected look object through the Interface Kit. The cited source does not show the factory running inside app_server. Treating the feature as if the server contained the add-on would give the wrong failure and trust boundary.
The fallback is useful but limited. The initializer handles a failed load, a missing factory symbol, or a factory that returns NULL. That is not a sandbox for arbitrary add-on code. A faulty or incompatible implementation can still fail while its methods are being called, so test a replacement in an isolated Haiku environment and preserve a known-good selection. Do not overwrite packaged system files to experiment.
A safe application-side appearance check
For application QA, first exercise the supported controls rather than calling be_control_look directly. In an attached test view, use a layout-managed pair of BButton objects to expose more than one visual state:
#include <Button.h>
#include <LayoutBuilder.h>
#include <Message.h>
#include <View.h>
enum {
MSG_APPLY = 'aply'
};
BView* content = new BView("look-smoke-test", B_WILL_DRAW);
BButton* apply = new BButton("apply", "Apply",
new BMessage(MSG_APPLY));
apply->MakeDefault(true);
BButton* unavailable = new BButton("unavailable", "Unavailable", NULL);
unavailable->SetEnabled(false);
BLayoutBuilder::Group<>(content, B_VERTICAL, B_USE_DEFAULT_SPACING)
.Add(apply)
.Add(unavailable);
Add content to a normal test window, then run the fixture after selecting the appearance you want to test; launch a fresh process for each run. Once the layout adopts the buttons, let the view hierarchy own them rather than deleting their pointers separately. Check keyboard focus, the default-button cue, disabled contrast, hover/pressed feedback, label clipping, and the buttons’ preferred sizes at a larger font. This exercises the same standard-control delegation path without making the application depend on the experimental look interface. The Interface Kit constructs its look object during initialization, so a live setting change should not be assumed to replace an already-created object in every running process.
If you are developing a control-look add-on, pin the build to the exact Haiku revision you target and verify its factory and drawing contract there. Test more than button states: menus, text controls, scrollbars, focus indicators, large fonts, translated labels, and layout insets all need coverage. Keep a simple fallback path and test the add-on in a disposable VM or snapshot before selecting it for a daily-use system. The public header’s warning is a compatibility boundary, not a promise that an add-on built against today’s source will remain binary-compatible with a later release.
Keep appearance separate from behavior
Use BButton, BCheckBox, and other standard controls when the goal is a native interface that follows the selected look. A custom-drawn BView is appropriate when the content itself is custom, but reproducing standard widgets by hard-coding colors and bevels bypasses the look’s state and sizing policy. Conversely, a control look is not a place to change what a button does: keep invocation targets, validation, and model updates in the application’s normal message flow.
The practical boundary is simple: application authors should test native controls under the appearances they support, while add-on authors must treat BControlLook as revision-sensitive code that runs during Interface Kit initialization. Pin the source revision, preserve the built-in fallback, and verify both visuals and metrics before relying on a custom look.
Related:
- Haiku BView Invalidation: Reconstructing Dirty Regions Without Flicker
- Haiku BControl: Values, Invocation Messages, and Enabled State
Sources:
- Haiku
BControlLookheader at revision388ca6823493ae937134949782be1eac9f5343b9 - Haiku Interface Kit control-look loading and fallback at revision
388ca6823493ae937134949782be1eac9f5343b9 - Haiku app_server control-look setting handler at revision
388ca6823493ae937134949782be1eac9f5343b9 - Haiku
BButtonpublic header at revision388ca6823493ae937134949782be1eac9f5343b9 - Haiku
BLayoutBuilderpublic header at revision388ca6823493ae937134949782be1eac9f5343b9 - Haiku
BButtondrawing and state delegation at revision388ca6823493ae937134949782be1eac9f5343b9 - Haiku built-in
HaikuControlLookimplementation at revision388ca6823493ae937134949782be1eac9f5343b9