Ever wondered how window that we see in the operating system which we drag here and there, is drawn. Well, then you are at right place. In this series we will demystify internals through source code in the haiku OS.
#1 Simple Application
app.cpp
#include <Application.h>
#include <Window.h>
TestApp::TestApp(void)
: BApplication("application/x-vnd.MyFirstApp")
{
BRect frame(100, 100, 500, 400);
Window* myWindow = new Window(frame, "My First App", B_TITLED_WINDOW, B_QUIT_ON_WINDOW_CLOSE, B_ALL_WORKSPACES);
myWindow->Show();
}
int main(){
TestApp *app = new TestApp();
app->Run();
delete app;
return 0;
}
app.h
#ifndef _TST_H
#define _TST_H
#include <Application.h>
class TestApp : public BApplication
{
public:
TestApp(void);
};
#endif
Above is the simple Test App that will output a window as given in the screenshot below:
Now, lets start our journey by diving into the code for drawing window.
Window Creation:
BRect frame(100, 100, 500, 400);
This line creates a BRect
object named frame
, representing the initial frame (position and size) of the application's main window. The parameters specify coordinates of the window:
100 = left
100 = top
500 = right
400 = bottom
BWindow* myWindow = new BWindow(frame, "My First App", B_TITLED_WINDOW, B_QUIT_ON_WINDOW_CLOSE, B_ALL_WORKSPACES);
This line creates a new Window
object, representing the main application window.
#2 Window Interface Constructor
This BWindow
constructor is defined in src/kits/interface/Window.cpp
.
BWindow::BWindow(BRect frame, const char* title, window_type type,
uint32 flags, uint32 workspace)
:
BLooper(title, B_DISPLAY_PRIORITY)
{
window_look look;
window_feel feel;
_DecomposeType(type, &look, &feel);
_InitData(frame, title, look, feel, flags, workspace);
}
This BWindow
constructor takes these paramters:
frame
: The initial position and size of the window.title
: The title of the window.type
: The type of window we need to create, It is anenum
.
// This enum is defined in headers/os/interface/Window.h
enum window_type {
B_UNTYPED_WINDOW = 0,
B_TITLED_WINDOW = 1,
B_MODAL_WINDOW = 3,
B_DOCUMENT_WINDOW = 11,
B_BORDERED_WINDOW = 20,
B_FLOATING_WINDOW = 21
};
flags
: Additional flags specifying the window behavior (e.g., whether the window is resizable or has a close button).
// This enum is too defined in headers/os/interface/Window.h
// window flags
enum {
B_NOT_MOVABLE = 0x00000001,
B_NOT_CLOSABLE = 0x00000020,
B_NOT_ZOOMABLE = 0x00000040,
B_NOT_MINIMIZABLE = 0x00004000,
B_NOT_RESIZABLE = 0x00000002,
B_NOT_H_RESIZABLE = 0x00000004,
B_NOT_V_RESIZABLE = 0x00000008,
B_AVOID_FRONT = 0x00000080,
B_AVOID_FOCUS = 0x00002000,
B_WILL_ACCEPT_FIRST_CLICK = 0x00000010,
B_OUTLINE_RESIZE = 0x00001000,
B_NO_WORKSPACE_ACTIVATION = 0x00000100,
B_NOT_ANCHORED_ON_ACTIVATE = 0x00020000,
B_ASYNCHRONOUS_CONTROLS = 0x00080000,
B_QUIT_ON_WINDOW_CLOSE = 0x00100000,
B_SAME_POSITION_IN_ALL_WORKSPACES = 0x00200000,
B_AUTO_UPDATE_SIZE_LIMITS = 0x00400000,
B_CLOSE_ON_ESCAPE = 0x00800000,
B_NO_SERVER_SIDE_WINDOW_MODIFIERS = 0x00000200
};
workspace
: Specified the workspace(s) where the window should be visible.
Now, let's try to understand what this constructor is doing:
Creates two object of window_look
and window_feel
// These enum are defined in headers/os/interface/Window.h
enum window_look {
B_BORDERED_WINDOW_LOOK = 20,
B_NO_BORDER_WINDOW_LOOK = 19,
B_TITLED_WINDOW_LOOK = 1,
B_DOCUMENT_WINDOW_LOOK = 11,
B_MODAL_WINDOW_LOOK = 3,
B_FLOATING_WINDOW_LOOK = 7
};
enum window_feel {
B_NORMAL_WINDOW_FEEL = 0,
B_MODAL_SUBSET_WINDOW_FEEL = 2,
B_MODAL_APP_WINDOW_FEEL = 1,
B_MODAL_ALL_WINDOW_FEEL = 3,
B_FLOATING_SUBSET_WINDOW_FEEL = 5,
B_FLOATING_APP_WINDOW_FEEL = 4,
B_FLOATING_ALL_WINDOW_FEEL = 6
};
_DecomposeType(type, &look, &feel);
It is responsible for decomposing the window_type
parameter passed to the BWindow
constructor into separate window_look
and window_feel
components.
void
BWindow::_DecomposeType(window_type type, window_look* _look,
window_feel* _feel) const
{
switch (type) {
case B_DOCUMENT_WINDOW:
*_look = B_DOCUMENT_WINDOW_LOOK;
*_feel = B_NORMAL_WINDOW_FEEL;
break;
case B_MODAL_WINDOW:
*_look = B_MODAL_WINDOW_LOOK;
*_feel = B_MODAL_APP_WINDOW_FEEL;
break;
case B_FLOATING_WINDOW:
*_look = B_FLOATING_WINDOW_LOOK;
*_feel = B_FLOATING_APP_WINDOW_FEEL;
break;
case B_BORDERED_WINDOW:
*_look = B_BORDERED_WINDOW_LOOK;
*_feel = B_NORMAL_WINDOW_FEEL;
break;
case B_TITLED_WINDOW:
case B_UNTYPED_WINDOW:
default:
*_look = B_TITLED_WINDOW_LOOK;
*_feel = B_NORMAL_WINDOW_FEEL;
break;
}
}
This function takes window_look
and window_feel
as pointer variable. On the basis of type
parameter and sets the appropriate window_look
and window_feel
.
_InitData(frame, title, look, feel, flags, workspace);
This line calls the _InitData()
function to initialize the internal data structures of the BWindow
object based on the provided parameters:
frame
: The initial position and size of the window.title
: The title of the window.look
andfeel
: The appearance and behavior of the window, determined by decomposing thetype
parameter.flags
: Additional flags specifying window behavior (e.g., whether the window is resizable or has a close button).workspace
: specifies the workspace(s) where the window should be visible.
void
BWindow::_InitData(BRect frame, const char* title, window_look look,
window_feel feel, uint32 flags, uint32 workspace, int32 bitmapToken)
{
STRACE(("BWindow::InitData()\n"));
if (be_app == NULL) {
debugger("You need a valid BApplication object before interacting with "
"the app_server");
return;
}
frame.left = roundf(frame.left);
frame.top = roundf(frame.top);
frame.right = roundf(frame.right);
frame.bottom = roundf(frame.bottom);
fFrame = frame;
if (title == NULL)
title = "";
fTitle = strdup(title);
_SetName(title);
fFeel = feel;
fLook = look;
fFlags = flags | B_ASYNCHRONOUS_CONTROLS;
fInTransaction = bitmapToken >= 0;
fUpdateRequested = false;
fActive = false;
fShowLevel = 1;
fTopView = NULL;
fFocus = NULL;
fLastMouseMovedView = NULL;
fKeyMenuBar = NULL;
fDefaultButton = NULL;
// Shortcut 'Q' is handled in _HandleKeyDown() directly, as its message
// get sent to the application, and not one of our handlers.
// It is only installed for non-modal windows, though.
fNoQuitShortcut = IsModal();
if ((fFlags & B_NOT_CLOSABLE) == 0 && !IsModal()) {
// Modal windows default to non-closable, but you can add the
// shortcut manually, if a different behaviour is wanted
AddShortcut('W', B_COMMAND_KEY, new BMessage(B_QUIT_REQUESTED));
}
// Edit modifier keys
AddShortcut('X', B_COMMAND_KEY, new BMessage(B_CUT), NULL);
AddShortcut('C', B_COMMAND_KEY, new BMessage(B_COPY), NULL);
AddShortcut('V', B_COMMAND_KEY, new BMessage(B_PASTE), NULL);
AddShortcut('A', B_COMMAND_KEY, new BMessage(B_SELECT_ALL), NULL);
// Window modifier keys
AddShortcut('M', B_COMMAND_KEY | B_CONTROL_KEY,
new BMessage(_MINIMIZE_), NULL);
AddShortcut('Z', B_COMMAND_KEY | B_CONTROL_KEY,
new BMessage(_ZOOM_), NULL);
AddShortcut('Z', B_SHIFT_KEY | B_COMMAND_KEY | B_CONTROL_KEY,
new BMessage(_ZOOM_), NULL);
AddShortcut('H', B_COMMAND_KEY | B_CONTROL_KEY,
new BMessage(B_HIDE_APPLICATION), NULL);
AddShortcut('F', B_COMMAND_KEY | B_CONTROL_KEY,
new BMessage(_SEND_TO_FRONT_), NULL);
AddShortcut('B', B_COMMAND_KEY | B_CONTROL_KEY,
new BMessage(_SEND_BEHIND_), NULL);
// We set the default pulse rate, but we don't start the pulse
fPulseRate = 500000;
fPulseRunner = NULL;
fIsFilePanel = false;
fMenuSem = -1;
fMinimized = false;
fMaxZoomHeight = 32768.0;
fMaxZoomWidth = 32768.0;
fMinHeight = 0.0;
fMinWidth = 0.0;
fMaxHeight = 32768.0;
fMaxWidth = 32768.0;
fLastViewToken = B_NULL_TOKEN;
// TODO: other initializations!
fOffscreen = false;
// Create the server-side window
port_id receivePort = create_port(B_LOOPER_PORT_DEFAULT_CAPACITY,
"w<app_server");
if (receivePort < B_OK) {
// TODO: huh?
debugger("Could not create BWindow's receive port, used for "
"interacting with the app_server!");
delete this;
return;
}
STRACE(("BWindow::InitData(): contacting app_server...\n"));
// let app_server know that a window has been created.
fLink = new(std::nothrow) BPrivate::PortLink(
BApplication::Private::ServerLink()->SenderPort(), receivePort);
if (fLink == NULL) {
// Zombie!
return;
}
{
BPrivate::AppServerLink lockLink;
// we're talking to the server application using our own
// communication channel (fLink) - we better make sure no one
// interferes by locking that channel (which AppServerLink does
// implicetly)
if (bitmapToken < 0) {
fLink->StartMessage(AS_CREATE_WINDOW);
} else {
fLink->StartMessage(AS_CREATE_OFFSCREEN_WINDOW);
fLink->Attach<int32>(bitmapToken);
fOffscreen = true;
}
fLink->Attach<BRect>(fFrame);
fLink->Attach<uint32>((uint32)fLook);
fLink->Attach<uint32>((uint32)fFeel);
fLink->Attach<uint32>(fFlags);
fLink->Attach<uint32>(workspace);
fLink->Attach<int32>(_get_object_token_(this));
fLink->Attach<port_id>(receivePort);
fLink->Attach<port_id>(fMsgPort);
fLink->AttachString(title);
port_id sendPort;
int32 code;
if (fLink->FlushWithReply(code) == B_OK
&& code == B_OK
&& fLink->Read<port_id>(&sendPort) == B_OK) {
// read the frame size and its limits that were really
// enforced on the server side
fLink->Read<BRect>(&fFrame);
fLink->Read<float>(&fMinWidth);
fLink->Read<float>(&fMaxWidth);
fLink->Read<float>(&fMinHeight);
fLink->Read<float>(&fMaxHeight);
fMaxZoomWidth = fMaxWidth;
fMaxZoomHeight = fMaxHeight;
} else
sendPort = -1;
// Redirect our link to the new window connection
fLink->SetSenderPort(sendPort);
STRACE(("Server says that our send port is %ld\n", sendPort));
}
STRACE(("Window locked?: %s\n", IsLocked() ? "True" : "False"));
_CreateTopView();
}
frame
: The initial position and size of the window.title
: The title of the window.look
: The appearance of the window, determined by the window look.feel
: The behavior of the window, determined by the window feel.flags
: Additional flags specifying window behavior (e.g., whether the window is resizable or has a close button).workspace
: Specifies the workspace(s) where the window should be visible.bitmapToken
: Token for an offscreen bitmap, used for offscreen drawing (optional).
frame.left = roundf(frame.left);
frame.top = roundf(frame.top);
frame.right = roundf(frame.right);
frame.bottom = roundf(frame.bottom);
This block of code rounds the coordinates of the window frame to integer values. Haiku OS expects window coordinates to be integers.
fFrame = frame;
This line assigns the rounded frame to the fFrame
member variable of the BWindow
object.
if (title == NULL)
title = "";
fTitle = strdup(title);
_SetName(title);
This block of code handles the window title. If the title is NULL
, it assigns an empty string to it. Then, it allocates memory for the title copies the provided title into it. Finally, it sets the the internal name of the window using the _SetName()
function.
//! Rename the handler and its thread
void
BWindow::_SetName(const char* title)
{
if (title == NULL)
title = "";
// we will change BWindow's thread name to "w>window title"
char threadName[B_OS_NAME_LENGTH];
strcpy(threadName, "w>");
#ifdef __HAIKU__
strlcat(threadName, title, B_OS_NAME_LENGTH);
#else
int32 length = strlen(title);
length = min_c(length, B_OS_NAME_LENGTH - 3);
memcpy(threadName + 2, title, length);
threadName[length + 2] = '\0';
#endif
// change the handler's name
SetName(threadName);
// if the message loop has been started...
if (Thread() >= B_OK)
rename_thread(Thread(), threadName);
}
fFeel = feel;
fLook = look;
fFlags = flags | B_ASYNCHRONOUS_CONTROLS;
These lines assigns the provided window feel, window look, and flags to their respective member variables of the BWindow
object. The B_ASYNCHRONOUS_CONTROLS
flags is added to the provided flags.
fInTransaction = bitmapToken >= 0;
fUpdateRequested = false;
fActive = false;
fShowLevel = 1;
These lines initializes various state variables of the BWindow
object, including whether it's currently in a transaction (i.e., updating its contents), whether an update has been requested, whether it's active, and its show level.
fTopView = NULL;
fFocus = NULL;
fLastMouseMovedView = NULL;
fKeyMenuBar = NULL;
fDefaultButton = NULL;
These lines initializes various pointers used for managing views, focus, the last view to receive mouse movement, the key menu bar, and the default button.
// Shortcut 'Q' is handled in _HandleKeyDown() directly, as its message
// get sent to the application, and not one of our handlers.
// It is only installed for non-modal windows, though.
fNoQuitShortcut = IsModal();
if ((fFlags & B_NOT_CLOSABLE) == 0 && !IsModal()) {
// Modal windows default to non-closable, but you can add the
// shortcut manually, if a different behaviour is wanted
AddShortcut('W', B_COMMAND_KEY, new BMessage(B_QUIT_REQUESTED));
}
This block of code sets up a keyboard shortcut for quitting the application (Cmd + W
) if the window is not modal and not marked as not closable.
These lines set up keyboard shortcuts for common actions like quitting the application (B_QUIT_REQUESTED). The handling of the 'Q' shortcut is explained in a comment, and a shortcut for quitting the application ('W') is added if the window is not modal and is not marked as unclosable.
// Edit modifier keys
AddShortcut('X', B_COMMAND_KEY, new BMessage(B_CUT), NULL);
AddShortcut('C', B_COMMAND_KEY, new BMessage(B_COPY), NULL);
AddShortcut('V', B_COMMAND_KEY, new BMessage(B_PASTE), NULL);
AddShortcut('A', B_COMMAND_KEY, new BMessage(B_SELECT_ALL), NULL);
// Window modifier keys
AddShortcut('M', B_COMMAND_KEY | B_CONTROL_KEY,
new BMessage(_MINIMIZE_), NULL);
AddShortcut('Z', B_COMMAND_KEY | B_CONTROL_KEY,
new BMessage(_ZOOM_), NULL);
AddShortcut('Z', B_SHIFT_KEY | B_COMMAND_KEY | B_CONTROL_KEY,
new BMessage(_ZOOM_), NULL);
AddShortcut('H', B_COMMAND_KEY | B_CONTROL_KEY,
new BMessage(B_HIDE_APPLICATION), NULL);
AddShortcut('F', B_COMMAND_KEY | B_CONTROL_KEY,
new BMessage(_SEND_TO_FRONT_), NULL);
AddShortcut('B', B_COMMAND_KEY | B_CONTROL_KEY,
new BMessage(_SEND_BEHIND_), NULL);
These lines set up additional keyboard shortcuts for common actions related to text editing (B_CUT, B_COPY, B_PASTE, B_SELECT_ALL
) and window management (_MINIMIZE_, _ZOOM_, B_HIDE_APPLICATION, _SEND_TO_FRONT_, _SEND_BEHIND_
).
// We set the default pulse rate, but we don't start the pulse
fPulseRate = 500000;
fPulseRunner = NULL;
fIsFilePanel = false;
fMenuSem = -1;
fMinimized = false;
fMaxZoomHeight = 32768.0;
fMaxZoomWidth = 32768.0;
fMinHeight = 0.0;
fMinWidth = 0.0;
fMaxHeight = 32768.0;
fMaxWidth = 32768.0;
fLastViewToken = B_NULL_TOKEN;
// TODO: other initializations!
fOffscreen = false;
These lines initialize additional member variables of the BWindow object, including pulse rate, window dimensions, file panel status, and other properties.
// Create the server-side window
port_id receivePort = create_port(B_LOOPER_PORT_DEFAULT_CAPACITY,
"w<app_server");
if (receivePort < B_OK) {
// TODO: huh?
debugger("Could not create BWindow's receive port, used for "
"interacting with the app_server!");
delete this;
return;
}
This section of the function creates a port for receiving messages from the app_server, which is responsible for managing windows and user interface elements in Haiku OS.
STRACE(("BWindow::InitData(): contacting app_server...\n"));
// let app_server know that a window has been created.
fLink = new(std::nothrow) BPrivate::PortLink(
BApplication::Private::ServerLink()->SenderPort(), receivePort);
if (fLink == NULL) {
// Zombie!
return;
}
These lines establish communication with the app_server
by creating a BPrivate::PortLink
object (fLink) that connects to the app_server's
sender port. This allows the BWindow
object to send messages to the app_server
and receive responses.
{
BPrivate::AppServerLink lockLink;
// we're talking to the server application using our own
// communication channel (fLink) - we better make sure no one
// interferes by locking that channel (which AppServerLink does
// implicetly)
if (bitmapToken < 0) {
fLink->StartMessage(AS_CREATE_WINDOW);
} else {
fLink->StartMessage(AS_CREATE_OFFSCREEN_WINDOW);
fLink->Attach<int32>(bitmapToken);
fOffscreen = true;
}
fLink->Attach<BRect>(fFrame);
fLink->Attach<uint32>((uint32)fLook);
fLink->Attach<uint32>((uint32)fFeel);
fLink->Attach<uint32>(fFlags);
fLink->Attach<uint32>(workspace);
fLink->Attach<int32>(_get_object_token_(this));
fLink->Attach<port_id>(receivePort);
fLink->Attach<port_id>(fMsgPort);
fLink->AttachString(title);
port_id sendPort;
int32 code;
if (fLink->FlushWithReply(code) == B_OK
&& code == B_OK
&& fLink->Read<port_id>(&sendPort) == B_OK) {
// read the frame size and its limits that were really
// enforced on the server side
fLink->Read<BRect>(&fFrame);
fLink->Read<float>(&fMinWidth);
fLink->Read<float>(&fMaxWidth);
fLink->Read<float>(&fMinHeight);
fLink->Read<float>(&fMaxHeight);
fMaxZoomWidth = fMaxWidth;
fMaxZoomHeight = fMaxHeight;
} else
sendPort = -1;
// Redirect our link to the new window connection
fLink->SetSenderPort(sendPort);
STRACE(("Server says that our send port is %ld\n", sendPort));
}
This part of the function constructs a message to create a new window on the app_server
. Depending on whether an offscreen bitmap is specified (bitmapToken >= 0
), the message type is set to create either a regular window or an offscreen window. Various parameters, such as the window frame, appearance, behavior, flags, workspace, and port IDs, are attached to the message. The message is then sent to the app_server
, and the function waits for a reply containing information about the created window, such as its frame size and limits.
fLink->FlushWithReply(code)
= This transfer the control to the app server
i.e ServerApp.cpp
FlushWithReply() = Will wait until the target replies unless a timeout value is specified in microseconds.
STRACE(("Window locked?: %s\n", IsLocked() ? "True" : "False"));
_CreateTopView();
}
This line of code prints a trace message indicating whether the window is locked. After initializing the window data, the _CreateTopView()
function is called to create the top-level view of the window.