Haiku BWindow Workspaces: Masks, Membership, and Activation Hooks
Set Haiku BWindow workspace masks safely, distinguish current-space sentinels from bitsets, and react to membership and activation callbacks.
Haiku exposes virtual-desktop placement directly on BWindow, but the API accepts both special sentinel values and ordinary 32-bit workspace masks. Confusing the two can make a window follow whichever workspace happens to be active when the app_server processes the request, instead of pinning it to the workspace the user selected. The related callbacks also answer different questions: one reports a change in membership, while the other reports activation of a workspace that already contains the window.
This guide covers the public Interface Kit contract and points out a few implementation details visible in the pinned Haiku source. It does not change the desktop’s active workspace on the user’s behalf. Test on the Haiku release you support because source implementation details can change independently of the public API description.
A workspace value is not always a bitset
The BWindow constructor and SetWorkspaces() accept a uint32 workspace value. The public Haiku header defines B_CURRENT_WORKSPACE as zero and B_ALL_WORKSPACES as 0xffffffff. Other values are masks: bit n represents workspace index n. The API documentation describes up to 32 potential workspaces and documents the two sentinel meanings.
That means zero is not an empty membership mask when passed to this API. It means “the current workspace.” In the current app_server implementation, a normal window’s B_CURRENT_WORKSPACE sentinel is converted to a one-bit mask for the server’s current workspace when the window is added or its workspace membership is set. If an app intends to choose a particular workspace, it should pass an explicit one-bit mask instead of passing the sentinel and assuming it names a previously captured workspace.
B_ALL_WORKSPACES is also a named special value. It is equivalent in result to enabling every supported workspace, but code should preserve its sentinel meaning rather than treating the value as an arbitrary mask to edit. For example, a routine that adds one workspace bit to an existing mask must not accidentally turn a deliberate all-workspaces setting into a narrower selection.
Build a mask from a checked workspace index
The Interface Kit exposes current_workspace() and count_workspaces(). Indices are zero-based in the current implementation, and the app_server maps index n to 1U << n. Validate both the live count and the representable mask width before shifting. This prevents an invalid or stale index from causing an undefined shift or a silently wrong placement.
#include <InterfaceDefs.h>
#include <SupportDefs.h>
#include <Window.h>
class WorkspaceWindow : public BWindow {
public:
using BWindow::BWindow;
void MoveToCapturedCurrentWorkspace()
{
const int32 workspaceCount = count_workspaces();
const int32 workspace = current_workspace();
if (workspaceCount < 1 || workspaceCount > 32
|| workspace < 0 || workspace >= workspaceCount
|| workspace >= 32) {
return;
}
const uint32 mask = static_cast<uint32>(1) << workspace;
SetWorkspaces(mask);
}
void WorkspacesChanged(uint32 oldMask, uint32 newMask) override
{
// Refresh workspace-specific presentation from newMask.
}
void WorkspaceActivated(int32 workspace, bool active) override
{
// Update transient UI only when this member workspace activates.
}
};
The example deliberately constructs a concrete mask. MoveToCapturedCurrentWorkspace() captures the index and immediately converts it to a bit; if the desktop configuration changes between a saved preference and a later operation, validate the saved index again. If the user chooses “follow the current workspace” as a continuing policy, pass B_CURRENT_WORKSPACE intentionally and document that it is a sentinel, not a stored index.
The example’s constructor inheritance is a compact sketch. A real application should use the constructor signature matching its window type, look, feel, flags, and frame. The mask API does not provide an error return from SetWorkspaces(), so do not treat the call as a status-bearing transaction. Read Workspaces() or observe WorkspacesChanged() when the application needs to update its model after membership changes.
Separate membership changes from workspace activation
WorkspacesChanged(oldWorkspaces, newWorkspaces) is the hook for a changed membership mask. Compare the old and new masks to determine which memberships changed. Do not use it as a general “desktop switched” notification: switching to another workspace does not necessarily change this window’s membership.
WorkspaceActivated(workspace, state) is called when a workspace containing the window becomes active or inactive. The state boolean distinguishes activation from deactivation. Haiku’s API documentation says the hook is only called for workspaces in which the window resides. An application that needs global desktop-switch monitoring cannot infer every switch from callbacks on one ordinary window, especially if that window is not a member of the activated workspace.
Keep callback work short. If activation requires refreshing a large document or querying a slow service, schedule bounded work instead of blocking the window’s message handling. For simple presentation state, record the index and active flag, then update controls without changing the desktop’s workspace count or activating another workspace.
Understand window flags and actual visibility
Workspace membership describes where a window can be displayed; it is not a promise that the window is currently visible, frontmost, or focused. Hidden state, stacking, subset windows, and the active workspace all affect what the user sees. Use the window’s normal visibility and activation APIs for those separate questions.
Three flags affect cross-workspace interaction and should not be confused with the membership mask:
B_NO_WORKSPACE_ACTIVATIONkeeps the current workspace active when a window on another workspace is activated.B_NOT_ANCHORED_ON_ACTIVATEcauses an anchored window to move to the current workspace when activated, according to the API documentation.B_SAME_POSITION_IN_ALL_WORKSPACESkeeps the window at the same position across workspaces.
These flags alter activation or positioning behavior; they do not mean “show on all workspaces.” Use B_ALL_WORKSPACES when that is the placement policy. The current app_server activation code also has ordering and special-window behavior, so test combinations on the exact Haiku version rather than deriving all behavior from a flag name.
One implementation caveat is especially useful when diagnosing a no-op: the pinned BWindow::SetWorkspaces() implementation returns without sending a request when the window feel is not B_NORMAL_WINDOW_FEEL. That is a statement about this source revision, not a general promise that every future Haiku release or every non-normal window behaves identically. Prefer normal application windows for this API and check the current target headers and implementation if using floating, modal, or subset window feels.
Verify behavior without disrupting the desktop
Use a small test application with a normal window and a workspace grid of at least two spaces. Log the index, count, requested mask, Workspaces() result, and both callback arguments. Test these cases separately:
- Construct with
B_CURRENT_WORKSPACE, then switch workspaces and confirm the window remains associated with the resolved workspace. - Call
SetWorkspaces(1U << index)for a checked index and verifyWorkspacesChanged()reports the membership change. - Call
SetWorkspaces(B_ALL_WORKSPACES)and verify the window is available in each configured workspace without assuming it is frontmost in each. - Observe
WorkspaceActivated()while entering and leaving a workspace that contains the window. Do not expect activation callbacks for unrelated workspaces. - Try boundary values in a disposable test window: index zero, the highest configured index, a negative saved index, an index equal to the configured count, and the 32-bit boundary.
- Repeat with any non-normal window feel or activation flags your app uses, recording the Haiku revision and separating public API expectations from observed implementation behavior.
Do not call activate_workspace() merely to test a window-placement helper. It changes global desktop state and can interrupt the user. Keep the configured workspace count under the user’s control, and make any test that changes it explicit and reversible. The robust design is to preserve whether the user chose a sentinel policy or a concrete mask, validate concrete indices before shifting, and keep membership, activation, visibility, and focus as distinct pieces of state.
Related:
- How to Use the Deskbar and Workspaces Effectively on Haiku
- Haiku BScreen: Display Geometry, Modes, and Safe Screen Access
Sources:
- Haiku BWindow API reference
- Haiku Window interface guide, pinned source revision
- Haiku public Window.h header, pinned source revision
- Haiku public InterfaceDefs.h header, pinned source revision
- Haiku BWindow implementation, pinned source revision
- Haiku app_server desktop implementation, pinned source revision