Skip to content
Haiku OSDeep Dive Published Updated 8 min readViews unavailable

How Haiku's Interface Kit and app_server Render Native Windows

How Haiku moves native windows from BApplication messages through BWindow and BView hierarchies to clipped, server-rendered output.

Haiku’s Interface Kit is not a collection of widgets painted independently by each process. It is a message-driven application API connected to a shared graphics service. A typical native program owns a BApplication, one or more BWindow objects, and a hierarchy of BView objects. The application handles user and system messages, while app_server maintains the server-side state needed to place windows, interpret drawing commands, clip overlapping content, and present the result.

That division gives native applications common behavior without forcing them to understand display hardware. It also imposes rules: windows have looper threads, views must be attached before most drawing operations are meaningful, UI objects are not freely mutated from arbitrary threads, and repainting happens through update regions. Following those rules produces responsive, correctly clipped interfaces. Violating them often creates freezes, flicker, stale pixels, or layouts that fail under another font or language.

The application and window message loops

BApplication represents the running graphical application and participates in the Application Kit’s looper/handler messaging model. BWindow also inherits from BLooper. Each window can dispatch BMessage objects to its handlers, including attached views. This is why a native Haiku interface is naturally event-oriented: keyboard input, mouse activity, commands from controls, scripted requests, and application-defined results can all arrive as messages rather than direct calls across unrelated components.

The window thread must remain available to dispatch those messages. If a button handler performs a long network transfer or blocks on slow storage, input and redraw work for that window wait behind it. The process may still be alive, and other applications may remain perfectly responsive, but the affected window appears frozen. Expensive work belongs on a worker thread or asynchronous service; completion should be posted back as a message containing the data needed to update the interface.

Threading does not make the window hierarchy lock-free. The BLooper contract supplies Lock() and Unlock() for code that must access the looper from another thread. A safer design minimizes such cross-thread access: workers own non-UI computation, the window thread owns views, and messages transfer immutable results or clear state changes. Short ownership boundaries are easier to reason about than a worker that holds a window lock while doing unrelated work.

BWindow and BView form a hierarchy, not a canvas shortcut

A BWindow defines the native top-level window and is responsible for view containment, focus, message dispatch, and its relationship with the Application Server. A BView derives from BHandler; it represents a region that can receive messages, draw content, and contain child views. Controls are built on this model rather than bypassing it.

Every view has geometry relative to its parent. Frame() and Bounds() answer different questions: a frame locates a view in the parent’s coordinate system, while bounds describe the view’s own internal coordinate system. Interface Kit conversion functions translate points and rectangles among view, parent, and screen coordinates. Keeping those spaces explicit prevents common bugs in hit testing, drag feedback, and custom drawing.

Constructing a view is not enough to make it drawable. The Be API documentation explains that a BView becomes known to the Application Server only after it is attached to a BWindow. At that point it receives attachment hooks and gains a server-side counterpart within the mirrored hierarchy. An unattached view can retain some cached graphics settings, but drawing, scrolling, clipping, and input operations that depend on server state require attachment.

Parent-child structure determines more than position. Moving a parent moves its descendants. The hierarchy participates in clipping, handler routing, and lifetime. A child view cannot simultaneously belong to two parents, and a view must be detached before independent destruction. Treating the hierarchy as ownership and behavior—not only visual nesting—avoids dangling handlers and inconsistent server state.

From invalidation to Draw()

Custom content is normally rendered in a BView::Draw(BRect updateRect) override. The system calls Draw() when some part of the view must be reconstructed: the window becomes visible, an obscured area is exposed, the view changes size, scrolling reveals new content, or application code invalidates an area. BView::Invalidate() schedules such an update for the entire view or a specified rectangle.

Invalidation is a request to repaint, not the repaint itself. The application should change its durable model first, then invalidate the affected region. When Draw() runs, it reads that model and produces the same pixels for the same state. This makes exposing, resizing, and delayed updates reliable. Drawing a one-time visual change directly from an event handler without preserving the state risks losing it the next time the system requests a redraw.

The supplied update rectangle bounds the damaged area, while the effective clipping region can be more exact. app_server already accounts for the visible portion of the view, overlapping children, window occlusion, and any additional clipping constraint. Code may query that region when drawing is expensive. Even without doing so, it should avoid traversing or rasterizing content that cannot intersect updateRect.

Over-invalidating is a performance defect. Repainting a complete editor or image view for a blinking caret wastes CPU and sends needless commands to the server. Under-invalidating is a correctness defect, leaving stale pixels. A useful custom view therefore maps model changes to precise rectangles and expands those rectangles only for effects that genuinely extend beyond the changed object, such as borders or antialiasing.

Graphics state and buffered server commands

BView exposes drawing state such as high and low colors, pen size and position, font, drawing mode, transform, and clipping. The state is associated with subsequent commands, so custom drawing should establish what it depends on rather than relying accidentally on a previous handler. The state stack can preserve and restore a caller’s environment around a self-contained operation.

Drawing instructions travel through the window’s connection to app_server, and that connection is buffered. Flush() submits pending commands without waiting for execution. Sync() submits them and waits for the server to finish the last queued instruction. Synchronous waiting is appropriate only when later program logic requires completed output. The API provides asynchronous bitmap and picture drawing calls specifically so several operations can be queued before one synchronization point.

This buffering also explains why “the call returned” and “the pixel is visible” are not identical statements. Most interface code should let the normal update cycle flush drawing. Code that renders to an off-screen target and immediately consumes the result may need explicit synchronization. Calling Sync() after every primitive turns a batched protocol into a series of round trips and can severely reduce throughput.

Layout APIs preserve native behavior

The Interface Kit includes Haiku’s Layout API, based on BLayout, BLayoutItem, and related classes. Layouts calculate view placement from minimum, preferred, and maximum sizes rather than assuming one fixed set of pixel coordinates. Layout-aware controls can respond to font metrics, translated labels, window resizing, and available space.

Fixed rectangles may look correct on the developer’s machine but clip a longer translation or a larger configured font. They also spread geometry arithmetic across event handlers, making later changes fragile. Layout containers centralize relationships—items in a row, column, grid, or grouped arrangement—and allow controls to communicate their sizing constraints.

Native appearance comes from using the actual controls, colors, fonts, spacing contracts, and focus behavior supplied by the kits. Copying a screenshot of a control reproduces one state at one scale; it does not reproduce keyboard navigation, enabled and disabled states, theme changes, or accessibility-relevant semantics. Custom drawing is valuable where the content is genuinely custom, not as a replacement for every standard control.

Input, focus, and rendering share the same model

Because the server knows view geometry and visible regions, it can associate pointer events with the relevant on-screen view. The window then dispatches messages within its handler hierarchy. Focus determines where keyboard input is directed, while controls convert lower-level events into higher-level command messages for their targets.

Custom views should keep input coordinates in the correct local space and invalidate only the feedback that changed. Dragging, hover states, and selection are model changes like any other display state. If pointer handling performs too much work, the event queue can lag behind the user’s hand; the API includes event-mask behavior for discarding older pointer history when an application explicitly prefers the newest position.

Input handlers must also avoid painting over the server’s clipping assumptions. Immediate feedback can be requested by changing state and invalidating, allowing the normal update path to remain authoritative. When immediate server submission is truly required, Flush() is lighter than waiting for completion with Sync().

A practical rendering checklist

When a native window misbehaves, first ask whether its looper thread is processing messages. Then verify that the view is attached, locked appropriately, and using the intended coordinate space. Confirm that model changes call Invalidate() for the complete damaged region and that Draw() reconstructs content from persistent state. Inspect clipping and graphics-state setup before suspecting the primitive itself.

For flicker or slowness, measure full-view invalidation, redundant drawing, synchronous server waits, and work performed on the window thread. For truncated or overlapping controls, test the Layout API with larger fonts and long translations. For symptoms across every application, move the investigation below one Interface Kit client toward app_server, its drawing engine, and the graphics driver.

The rendering pipeline is therefore a collaboration: loopers keep state changes serialized, views describe hierarchy and drawing, the update mechanism limits work, and app_server resolves shared desktop visibility. Applications that cooperate with each layer get the responsive, consistent behavior associated with Haiku’s native interface.

Related:

Sources:

Comments