ARM64 OS Handbook
🔍

Chapter 41: Window Manager

What You Will Learn in This Chapter
  • What a window manager does and how it organizes the screen
  • How to manage overlapping windows with a Z-order list
  • How clipping and damage regions prevent rendering waste
  • How to forward keyboard and mouse events to the correct window
  • How to implement a simple compositor
  • Our minimal window manager implementation

41.1 What Is a Window Manager?

A window manager is a user-space program that controls the placement and appearance of windows on the screen. It sits between applications (clients) and the framebuffer, managing screen real estate and user input.

The window manager is responsible for:

  • Layout: deciding where each window appears and how big it is
  • Decoration: drawing title bars, borders, and close buttons
  • Z-ordering: deciding which window is on top when they overlap
  • Input routing: sending mouse clicks and keyboard events to the correct window
  • Resizing and moving: allowing the user to rearrange windows interactively

41.2 Window Data Structure

Each window is represented by a struct that stores its position, size, content, and state:

/* Window flags */
#define WF_VISIBLE    (1 << 0)
#define WF_FOCUSED    (1 << 1)
#define WF_MINIMIZED  (1 << 2)
#define WF_MOVING     (1 << 3)

typedef struct window {
    int id;
    char title[64];
    int x, y;          /* Position on screen */
    int width, height; /* Dimensions */
    int flags;         /* WF_* flags */
    uint32_t *buffer;  /* Window content buffer (offscreen) */
    struct window *next;  /* Linked list (Z-order) */
} window_t;

/* Window list head (topmost window first) */
static window_t *windows = NULL;
static int next_window_id = 1;

41.3 Adding and Removing Windows

window_t *create_window(const char *title, int x, int y,
                         int w, int h) {
    window_t *win = malloc(sizeof(window_t));
    win->id = next_window_id++;
    strncpy(win->title, title, sizeof(win->title) - 1);
    win->x = x;
    win->y = y;
    win->width = w;
    win->height = h;
    win->flags = WF_VISIBLE;
    win->buffer = malloc(w * h * 4);  /* BGRA32 buffer */
    memset(win->buffer, 0, w * h * 4);

    /* Insert at front of Z-order (topmost) */
    win->next = windows;
    windows = win;
    return win;
}

void destroy_window(window_t *win) {
    /* Find and remove from list */
    window_t **pp = &windows;
    while (*pp) {
        if (*pp == win) { *pp = win->next; break; }
        pp = &(*pp)->next;
    }
    free(win->buffer);
    free(win);
    redraw_screen();
}

41.4 Compositing: Rendering Windows to Screen

The window manager uses a compositing approach: each window has its own off-screen buffer. The compositor blends all window buffers together in Z-order to produce the final screen image:

/* Redraw the entire screen by compositing all visible windows */
void redraw_screen(void) {
    /* Clear back buffer to desktop background */
    fb_clear_back_buffer(bg_color);

    /* Draw each window from bottom to top (traverse reversed list) */
    window_t *stack[64];
    int count = 0;
    for (window_t *w = windows; w; w = w->next) {
        stack[count++] = w;
    }

    for (int i = count - 1; i >= 0; i--) {
        window_t *win = stack[i];
        if (!(win->flags & WF_VISIBLE)) continue;

        /* Copy window buffer to screen at (win->x, win->y) */
        for (int row = 0; row < win->height; row++) {
            for (int col = 0; col < win->width; col++) {
                int sx = win->x + col;
                int sy = win->y + row;
                if (sx >= 0 && sx < (int)fb.width &&
                    sy >= 0 && sy < (int)fb.height) {
                    fb_put_pixel_buffered(sx, sy,
                        win->buffer[row * win->width + col]);
                }
            }
        }

        /* Draw title bar and border decorations */
        draw_title_bar(win);
        draw_border(win);
    }

    fb_flip();
}

41.5 Window Decorations

Each window gets a title bar at the top and a thin border. The title bar shows the window title and close/minimize buttons:

#define TITLE_BAR_HEIGHT  24
#define BORDER_WIDTH       2

void draw_title_bar(window_t *win) {
    /* Fill title bar background */
    pixel_t bar_color = (win->flags & WF_FOCUSED) ?
                        rgb(200, 200, 200) : rgb(100, 100, 100);
    fb_fill_rect(win->x, win->y - TITLE_BAR_HEIGHT,
                 win->width, TITLE_BAR_HEIGHT, bar_color);

    /* Draw title text */
    fb_put_string(win->x + 4, win->y - TITLE_BAR_HEIGHT + 4,
                  win->title, BLACK, 0xFFFFFFFF);
}

void draw_border(window_t *win) {
    /* Top border (just above title bar, or at win->y if no title bar) */
    int top = win->y;
    for (int b = 0; b < BORDER_WIDTH; b++) {
        fb_rect_outline(win->x - b, top - TITLE_BAR_HEIGHT - b,
                        win->width + 2*b, win->height + TITLE_BAR_HEIGHT + 2*b,
                        rgb(150, 150, 150));
    }
}

41.6 Event Routing

When the user clicks or presses a key, the window manager determines which window receives the event. Mouse events use spatial lookup; keyboard events go to the focused window:

/* Find the topmost visible window at screen position (mx, my) */
window_t *window_at(int mx, int my) {
    for (window_t *w = windows; w; w = w->next) {
        if (!(w->flags & WF_VISIBLE)) continue;
        /* Check the title bar area too */
        if (mx >= w->x && mx < w->x + w->width &&
            my >= w->y - TITLE_BAR_HEIGHT && my < w->y + w->height) {
            return w;
        }
    }
    return NULL;
}

/* Handle a mouse click event */
void handle_mouse_click(int mx, int my, int button) {
    window_t *win = window_at(mx, my);
    if (!win) return;

    /* Raise window to top of Z-order */
    raise_window(win);

    /* Set focus */
    set_focus(win);

    /* Check if click is on close button */
    if (my < win->y && mx >= win->x + win->width - 20) {
        destroy_window(win);
        return;
    }

    /* Check if click is on title bar (initiate move) */
    if (my < win->y) {
        begin_move_window(win, mx, my);
        return;
    }

    /* Forward event to window's application */
    send_event(win, EVENT_MOUSE_CLICK, mx, my, button);
}

/* Set keyboard focus to a window */
void set_focus(window_t *win) {
    for (window_t *w = windows; w; w = w->next) {
        w->flags &= ~WF_FOCUSED;
    }
    win->flags |= WF_FOCUSED;
    redraw_screen();
}

/* Raise window to top of Z-order */
void raise_window(window_t *win) {
    /* Remove from current position */
    window_t **pp = &windows;
    while (*pp) {
        if (*pp == win) { *pp = win->next; break; }
        pp = &(*pp)->next;
    }
    /* Insert at front */
    win->next = windows;
    windows = win;
    redraw_screen();
}

41.7 Client Communication

Applications communicate with the window manager through a simple IPC protocol (pipes or Unix domain sockets). The protocol uses fixed-size messages:

/* Window manager protocol messages */
#define WM_CREATE_WINDOW  1
#define WM_DESTROY_WINDOW 2
#define WM_RESIZE_WINDOW  3
#define WM_MOVE_WINDOW    4
#define WM_SET_TITLE      5
#define WM_DRAW_PIXEL     6
#define WM_FILL_RECT      7
#define WM_GET_EVENT      8

typedef struct {
    uint32_t type;
    int32_t  x, y, w, h;
    uint32_t color;
    char     title[64];
} wm_msg_t;

/* Application: draw a pixel on our window */
void wm_draw_pixel(wm_msg_t *msg, int x, int y, uint32_t color) {
    msg->type = WM_DRAW_PIXEL;
    msg->x = x;
    msg->y = y;
    msg->color = color;
    write(wm_socket_fd, msg, sizeof(wm_msg_t));
}

/* Application: get next event (blocks if none pending) */
int wm_get_event(wm_msg_t *msg) {
    return read(wm_socket_fd, msg, sizeof(wm_msg_t));;
}

41.8 Our Implementation

Our window manager (/bin/wm) runs as a user-space process and provides:

  • Compositing window manager: all windows rendered into off-screen buffers, composited in Z-order
  • Window decorations: title bar with title text, close button, thin border
  • Z-order management: raise window on click, topmost list
  • Keyboard focus: focused window receives keyboard events
  • Window move: drag title bar to move a window
  • Client protocol: simple IPC over pipes with fixed-size messages
  • Damage regions: only redraw changed areas (optimization)

The window manager is started as a user process after the shell is running. It reads from the keyboard and mouse input devices and renders the desktop on the framebuffer.

41.9 Exercises

Exercise 1: Window Resize

Add a resize handle to the bottom-right corner of each window. When dragged, the window changes size and its contents are re-requested from the client.

Exercise 2: Damage Regions

Instead of redrawing the entire screen on every change, track which rectangles changed and only composite those regions. Measure the performance improvement.

Exercise 3: Keyboard Shortcuts

Add Alt-Tab to cycle through open windows and Alt-F4 to close the focused window.

41.10 Summary

A window manager organizes the screen into overlapping rectangular regions called windows. It composites window buffers in Z-order, decorates them with title bars and borders, and routes user input to the correct application. Our compositing window manager communicates with applications through a simple IPC protocol and provides the visual foundation for a graphical user interface.