Lumen Compositor

Lumen is the Aegis display server and window compositor. It renders directly to the linear framebuffer mapped via sys_fb_map (syscall 513), bypassing X11, Wayland, and any display protocol entirely.

v1 note: Lumen is v1 software – functional and tested, but not production-hardened. Contributions are welcome – file issues or propose changes at exec/aegis. Lumen owns the framebuffer surface, manages window Z-order, performs dirty-rect compositing with frosted glass effects, handles mouse/keyboard input dispatch, and hosts the built-in terminal emulator.

+-------------------------------------------------------+
|  Bastion (display manager)                            |
|    authenticates user -> fork+execve -> Lumen         |
+-------------------------------------------------------+
         |
         v
+-------------------------------------------------------+
|  Lumen compositor (user/bin/lumen/)                   |
|  +---------------------------------------------------+|
|  | Framebuffer (sys_fb_map, syscall 513)             ||
|  |   fb_info_t: addr, width, height, pitch, bpp      ||
|  +---------------------------------------------------+|
|  | Back buffer (malloc'd, same dimensions)           ||
|  +---------------------------------------------------+|
|  | compositor_t                                      ||
|  |   window stack [MAX_WINDOWS=16]                   ||
|  |   dirty rect accumulator [MAX_DIRTY_RECTS=32]     ||
|  |   cursor state, drag state, selection state       ||
|  +---------------------------------------------------+|
|  | Citadel top bar (libcitadel.a, in-process)       ||
|  |   on_draw_desktop callback -> topbar_draw         ||
|  | Context menu (Aegis system menu, in-process)      ||
|  |   on_draw_overlay callback -> menu_draw           ||
|  +---------------------------------------------------+|
|  | Glyph toolkit (widget windows)                    ||
|  | Dropdown terminal (in-process, Ctrl+Alt+T)        ||
|  +---------------------------------------------------+|
|  | AF_UNIX server /run/lumen.sock                    ||
|  |   citadel-dock (panel)                            ||
|  |   /bin/terminal, /apps/* (windows)                ||
|  +---------------------------------------------------+|
+-------------------------------------------------------+

Architecture Overview

Lumen is a single-process, single-threaded compositor. All input polling, PTY I/O, compositing, and framebuffer writes happen in one for (;;) event loop running at approximately 60 fps (16ms sleep when idle).

In-process vs external clients

Lumen has two categories of window content:

In-process built-ins are glyph_window_t objects allocated directly inside the Lumen process. They share the compositor’s back buffer without any IPC overhead. The in-process built-ins are:

  • Dropdown terminal (Ctrl+Alt+T) — thin wrapper around the glyph_term emulator core in libglyph
  • Widget test window — reachable via LUMEN_OP_INVOKE "widgets"
  • About window — shown on every Lumen startup

External clients connect to Lumen over an AF_UNIX socket at /run/lumen.sock. They paint into a memfd-backed pixel buffer passed to them via SCM_RIGHTS, then send LUMEN_OP_DAMAGE to trigger recompositing. External clients include /bin/citadel-dock (the application dock panel), /bin/terminal (the standalone terminal), /bin/applications (fullscreen app launcher), and all /apps/* bundles. This is the primary growth path for the GUI — new functionality is added as external clients, not linked into Lumen.

The external window protocol is defined in user/lib/glyph/lumen_proto.h; the client library is lumen_client.{c,h}; the server side is user/bin/lumen/lumen_server.{c,h}.

Framebuffer Initialization

Lumen maps the hardware framebuffer via a custom syscall:

typedef struct {
    uint64_t addr;
    uint32_t width, height, pitch, bpp;
} fb_info_t;

fb_info_t fb_info;
long ret = syscall(513, &fb_info);  /* sys_fb_map */

uint32_t *fb = (uint32_t *)(uintptr_t)fb_info.addr;
int pitch_px = (int)(fb_info.pitch / (fb_info.bpp / 8));

The framebuffer is a linear array of 32-bit XRGB pixels. pitch is in bytes; all internal surface operations use pitch_px (pixels per row) to handle stride correctly. A separate back buffer of identical dimensions is heap-allocated for double buffering.

Compositor State

The central data structure is compositor_t, defined in compositor.h:

typedef struct {
    surface_t fb;                           /* framebuffer surface */
    surface_t back;                         /* back buffer surface */
    glyph_window_t *windows[MAX_WINDOWS];   /* Z-ordered window stack */
    int nwindows;
    glyph_window_t *focused;                /* keyboard focus target */
    int cursor_x, cursor_y;
    int dragging;                           /* titlebar drag in progress */
    glyph_window_t *drag_win;
    int drag_dx, drag_dy;                   /* offset from cursor to window origin */
    glyph_window_t *content_drag_win;       /* window receiving mouse drag (text selection) */
    int prev_buttons;
    int selecting;                          /* desktop selection box active */
    int sel_x0, sel_y0, sel_x1, sel_y1;
    glyph_rect_t dirty_rects[MAX_DIRTY_RECTS];
    int ndirty;
    int full_redraw;                        /* force complete recomposite */
    int bg_rendered;
    wallpaper_t wallpaper;
    void (*on_draw_desktop)(surface_t *back, int w, int h);
    void (*on_draw_overlay)(surface_t *back, int w, int h);
} compositor_t;

Window Stack

Windows are stored in a flat array ordered by Z-depth: windows[0] is the bottom-most, windows[nwindows-1] is the top-most. The limit is MAX_WINDOWS = 16. Window operations:

Function Behavior
comp_add_window Append to stack, set as focused, trigger full redraw
comp_remove_window Mark old screen rect dirty, compact array, destroy window
comp_raise_window Move window to top of stack, trigger full redraw
comp_window_at Reverse-iterate stack to find topmost visible window at (x,y)

Focus tracking is manual: comp_add_window sets focused_window = 1 on the new window. When focus changes, the old window’s flag is cleared and the new window’s flag is set. This flag is read by render_chrome to decide whether to draw the close button “x” glyph.

Dirty Rect Compositing

Lumen uses a dirty-rect accumulator to minimize per-frame work. The compositor maintains up to MAX_DIRTY_RECTS = 32 rectangles. When the accumulator overflows, the last entry is expanded via glyph_rect_union to absorb new rects.

Dirty Rect Pipeline

 Per-frame dirty rect flow:
 +--------------------------------------------------+
 | 1. Collect dirty rects from windows              |
 |    glyph_window_get_dirty_rect() -> screen coords|
 +--------------------------------------------------+
           |
           v
 +--------------------------------------------------+
 | 2. Full redraw path (if full_redraw == 1)        |
 |    - Draw wallpaper/solid bg to entire backbuf   |
 |    - Call on_draw_desktop (top bar)              |
 |    - Render + blit ALL visible windows           |
 |    - Draw selection box                          |
 |    - Call on_draw_overlay (context menu)         |
 |    - memcpy entire backbuf -> framebuffer        |
 +--------------------------------------------------+
           |  (if not full redraw)
           v
 +--------------------------------------------------+
 | 3. Partial redraw path                           |
 |    For EACH dirty rect:                          |
 |      a. Restore background in that rect          |
 |    Then once:                                    |
 |      b. Call on_draw_desktop                     |
 |      c. Re-render windows overlapping any rect   |
 |      d. Call on_draw_overlay                     |
 |    For EACH dirty rect:                          |
 |      e. partial_flip: memcpy rect from back->fb  |
 +--------------------------------------------------+

The partial redraw path processes each dirty rect individually rather than unioning them into one bounding box. From compositor.c:

/* Process each dirty rect individually instead of unioning into one
 * giant bounding box. This avoids redrawing the entire horizontal
 * span between two small dirty regions on opposite sides. */

partial_flip

The partial_flip function copies only the dirty rectangle from the back buffer to the framebuffer, row by row:

static void
partial_flip(surface_t *fb, surface_t *back, glyph_rect_t r)
{
    for (int y = r.y; y < r.y + r.h && y < fb->h; y++) {
        int x0 = r.x < 0 ? 0 : r.x;
        int x1 = r.x + r.w;
        if (x1 > fb->w) x1 = fb->w;
        int count = x1 - x0;
        if (count <= 0) continue;
        memcpy(&fb->buf[y * fb->pitch + x0],
               &back->buf[y * back->pitch + x0],
               (unsigned)count * sizeof(uint32_t));
    }
}

Frosted Glass Compositing

Lumen implements a multi-layer frosted glass effect for windows, the dock, the top bar, and the context menu. The effect is achieved by:

  1. Box blur the backbuffer region under the element (draw_box_blur, radius 10)
  2. Color tint via alpha-blended rectangle (draw_blend_rect)
  3. Color-keyed blit of window content (draw_blit_keyed)

Blit Modes

Window blitting uses three modes to balance visual quality and performance:

Mode Constant Behavior When Used
Full frost BLIT_FROST Blur + tint + keyed blit Normal compositing
Fast frost BLIT_FAST_FROST Tint + keyed blit (skip blur) Non-dragged windows during drag
Opaque BLIT_OPAQUE Direct surface blit Dragged window during drag

The frosted window rendering pipeline (from blit_window_to_back):

 Frosted window blit (BLIT_FROST):
 +------------------------------------------+
 | 1. Box blur entire window footprint      |
 |    draw_box_blur(back, x, y, w, h, 10)   |
 +------------------------------------------+
 | 2. Dark tint on titlebar region          |
 |    draw_blend_rect(..., 0x101020, 160)   |
 +------------------------------------------+
 | 3. Tint on client region                 |
 |    Terminal: dark glass (0x0A0A14, 160)  |
 |    Widget:  dark glass (0x181828, 150)   |
 +------------------------------------------+
 | 4. Subtle border (1px highlight/shadow)  |
 +------------------------------------------+
 | 5. Title text (centered, white)          |
 +------------------------------------------+
 | 6. Traffic-light circles (close/min/max) |
 +------------------------------------------+
 | 7. Color-keyed client area blit          |
 |    Key = C_TERM_BG (terminal) or         |
 |          C_SHADOW  (widget window)       |
 +------------------------------------------+

The color key mechanism allows transparent pixels in the window surface to reveal the frosted background underneath. Terminal windows use C_TERM_BG (0x000A0A14) as the key color; widget windows use C_SHADOW (0x00080810).

Chromeless Frosted Windows

The dropdown terminal uses chromeless = 1 to skip the titlebar entirely. Its frosted glass path is simpler: blur, tint with C_TERM_BG, then color-keyed blit of the terminal content.

Cursor System

The cursor is a 16x20 ARGB sprite rendered procedurally at startup (cursor.c). It uses a save-under strategy:

  1. Before drawing: save the framebuffer pixels under the cursor position to s_save[]
  2. Alpha-blend the cursor sprite onto the framebuffer
  3. Before the next composite: restore s_save[] to erase the cursor

The compositor calls cursor_hide() before any framebuffer write and cursor_show() after. Mouse-only movement (no content changes) is optimized to skip the full composite cycle, using only cursor_hide() + cursor_show() to relocate the cursor.

The cursor sprite has three layers built procedurally:

  • Shadow (alpha 0x40, offset +1,+1)
  • Outline (black, fully opaque border pixels)
  • Fill (white, fully opaque interior)

Mouse movement applies a 1.5x speed multiplier via integer math: cursor_x += dx + dx / 2.

Input Handling

Keyboard

Lumen sets stdin to raw mode (VMIN=0, VTIME=0, ICANON and ECHO disabled). Single bytes are read per iteration. Escape sequences are collected with tight retry loops (up to 80 retries per byte) to handle the keyboard ISR’s atomic multi-byte push.

Key bindings processed by the compositor:

Sequence Action
Ctrl+Alt+T (ESC + 0x14) Toggle dropdown terminal visibility
Ctrl+Alt+I (ESC + 0x09) Spawn gui-installer as an external Lumen client (added in 1.0.2)
Ctrl+Alt+L (ESC + 0x0C) Lock screen (signals parent Bastion via SIGUSR1)
Alt+C or Ctrl+Shift+C Copy terminal selection to clipboard
Alt+V or Ctrl+Shift+V Paste clipboard to focused PTY
CSI sequences (ESC [ …) Forward to the focused window. PTY-backed windows (tag >= 0) get the raw bytes written to the PTY master fd. Proxy windows (tag == -1, external clients) get arrow-key sequences translated to synthetic single-byte codes 0xF1-0xF4 (Up/Down/Right/Left) and dispatched via the window’s on_key callback — ASCII / UTF-8 don’t use that range so it’s a clean side channel until the protocol grows a real “raw key” event.
Normal keys If the focused window has an on_key callback (proxy windows), call it; otherwise forward to the focused PTY master fd.

Mouse

Mouse events are read from /dev/mouse (non-blocking). Events are batched: all pending events in a single frame are accumulated into a total delta before processing. The event structure:

typedef struct __attribute__((packed)) {
    uint8_t  buttons;
    int16_t  dx;
    int16_t  dy;
    int16_t  scroll;
} mouse_event_t;

Mouse dispatch priority (from comp_handle_mouse and the main loop):

  1. Context menu hit testing (click on menu item, or outside to close)
  2. Top bar “Aegis” click (opens/closes context menu)
  3. Desktop selection box (drag on empty space)
  4. Content drag (text selection in terminal)
  5. Titlebar drag (window move)
  6. Window close button (traffic-light red circle)
  7. Window focus change + raise
  8. Widget dispatch (client-area click forwarded to Glyph widget tree)

Wallpaper

Wallpaper is loaded from /usr/share/wallpaper.raw as a simple raw pixel format: 8-byte header (uint32_t width, uint32_t height) followed by XRGB pixel data. If the wallpaper dimensions match the framebuffer exactly, rows are memcpy‘d directly. Otherwise, draw_blit_scaled performs nearest-neighbor scaling.

External Window Protocol

Phase 47 added an AF_UNIX protocol that lets out-of-process binaries open windows on Lumen. The first real client was /bin/gui-installer; the dock followed in Phase 47b as /bin/citadel-dock; standalone terminal and app bundles followed in 1.2.0. The protocol header lives at user/lib/glyph/lumen_proto.h; the client side is lumen_client.{c,h}; the server side is user/bin/lumen/lumen_server.{c,h}.

Handshake

Lumen listens at /run/lumen.sock. A new client connects, sends a lumen_hello_t (magic 0x4c4d454e = "LMEN", version 1), and gets a status reply. Until accept(2) runs, Aegis’s AF_UNIX returns ECONNREFUSED to connect(2); clients must therefore retry first-connect (the gui-installer retries up to 50 times with a 100 ms sleep between attempts).

Opcodes (client → server)

Opcode Value Purpose
LUMEN_OP_CREATE_WINDOW 1 Open a normal decorated window. Reply contains the assigned (width, height, x, y) plus a memfd file descriptor for the pixel buffer (passed via SCM_RIGHTS). Pass LUMEN_WIN_FLAG_FULLSCREEN to get a chromeless, framebuffer-sized, focused window at 0,0 — used by the Applications launcher.
LUMEN_OP_DAMAGE 2 Tell the compositor that the shared pixel buffer has changed and should be recomposited.
LUMEN_OP_SET_TITLE 3 Update the window’s title bar text.
LUMEN_OP_DESTROY_WINDOW 4 Tear the window down.
LUMEN_OP_CREATE_PANEL 5 Open a chromeless, non-focusable, bottom-anchored panel. Used by citadel-dock.
LUMEN_OP_INVOKE 6 Ask Lumen to spawn an app by name. "widgets" is an in-process built-in; "applications" spawns /bin/applications; everything else resolves through the /apps bundle registry via glyph_apps_find and spawns the bundle’s ELF.
LUMEN_OP_DRAG_START 7 Begin a compositor-brokered drag-and-drop operation. The compositor draws a ghost label, routes LUMEN_EV_DRAG_OVER/LUMEN_EV_DRAG_LEAVE to the proxy window under the pointer, and delivers LUMEN_EV_DROP on mouse release.

Events (server → client)

Event Value Purpose
LUMEN_EV_KEY 0x10 Single-byte keystroke (or one of the synthetic arrow codes 0xF1-0xF4) targeted at the client’s window when it has focus.
LUMEN_EV_MOUSE 0x11 Mouse event (button mask, position, scroll) when the cursor is inside the client’s window. evtype is one of LUMEN_MOUSE_MOVE, LUMEN_MOUSE_DOWN, LUMEN_MOUSE_UP, LUMEN_MOUSE_WHEEL.
LUMEN_EV_CLOSE_REQUEST 0x12 The user clicked the close button; the client should clean up and exit. (Proxy windows wire win->on_close so the close button forwards this event instead of self-destructing the proxy.)
LUMEN_EV_FOCUS 0x13 Focus gained/lost.
LUMEN_EV_RESIZED 0x14 Reserved; never sent in v1.
LUMEN_EV_DRAG_OVER 0x15 Pointer is over this window during a drag-and-drop operation.
LUMEN_EV_DRAG_LEAVE 0x16 Pointer left this window during a drag-and-drop operation.
LUMEN_EV_DROP 0x17 Mouse released; payload (file path + DnD op) delivered here.

Pixel buffer

The CREATE reply hands the client a memfd backing the window’s pixel buffer. The client maps it MAP_SHARED, paints into it, and sends LUMEN_OP_DAMAGE to tell the compositor a rect needs recompositing. Lumen avoids per-frame copies by reading directly from that shared memory during composite.

Cleanup invariants

These have been bug-class hazards in the past and the current code preserves them:

  • comp_remove_window(comp, win) already calls glyph_window_destroy(win) internally; never call glyph_window_destroy again afterward in proxy hangup paths or you double-free.
  • comp_remove_window clears comp->focused, comp->drag_win, and comp->content_drag_win if any of them pointed at the removed window. Any other code holding a glyph_window_t * outside the windows[] array must clear it on add/raise.
  • For proxy windows, pw->shared (the memfd map) is munmapped on hangup. Anything still pointing at the map after that will fault on the next event with CR2 in the mmap region.
  • glyph_window_t.tag is initialized to -1 (sentinel). Lumen uses tag >= 0 to mean “deliver via direct PTY write”. Default 0 from calloc previously collided with valid PTY fd 0.

LUMEN_RUNNING=1

Lumen exports LUMEN_RUNNING=1 in its environment so a recursively-launched /bin/lumen exits with "you're already using lumen, pal" instead of trying to grab the framebuffer twice.

Built-in vs external clients

Client Status Notes
Dropdown terminal (Ctrl+Alt+T) In-process Thin wrapper over glyph_term core in libglyph; creates one glyph_window_t at startup, starts hidden.
Widget test diagnostic In-process Reachable via LUMEN_OP_INVOKE "widgets".
About window In-process Shown at every Lumen startup.
citadel-dock External (Phase 47b) Vigil graphical-mode service. Connects and creates a panel (LUMEN_OP_CREATE_PANEL). 7 items normally, 8 on live media (adds installer).
/bin/terminal External (Phase 47b) Standalone terminal client using glyph_term via libglyph. Spawned by citadel-dock clicking the terminal icon.
/bin/applications External (1.2.0) Fullscreen launcher grid (LUMEN_WIN_FLAG_FULLSCREEN). Spawned via LUMEN_OP_INVOKE "applications".
/apps/* bundles External (1.2.0) All app bundles (settings, calculator, editor, filemanager, sysmon, gui-installer, …) connect as external clients. Resolved by glyph_apps_find in invoke_handler.

Terminal Emulator (Dropdown)

The dropdown terminal (Ctrl+Alt+T) is the only terminal window that runs in-process inside Lumen. Regular terminal windows are provided by the standalone /bin/terminal external client (Phase 47b).

Architecture

The emulator core lives in libglyph as glyph_term_t (user/lib/glyph/glyph_term.{c,h}). Both the in-process dropdown and the external /bin/terminal binary share this core. The in-process terminal.c in Lumen is a thin wrapper that creates one glyph_window_t, instantiates a glyph_term_t in its priv field, and guards every entry point against being called with a proxy window’s priv pointer (which is a proxy_window_t, not a glyph_term_t).

The terminal grid is a ring buffer of glyph_term_cell_t cells:

typedef struct {
    char ch;
    uint32_t fg;    /* 0xRRGGBB or GLYPH_TERM_DEFAULT_COLOR */
    uint32_t bg;    /* 0xRRGGBB or GLYPH_TERM_DEFAULT_COLOR */
    uint8_t  attrs; /* GLYPH_TERM_ATTR_BOLD | ATTR_UNDERLINE | ATTR_REVERSE */
} glyph_term_cell_t;

The ring buffer holds rows + GLYPH_TERM_SCROLLBACK (500 lines of history) rows. scroll_top tracks the first visible row. glyph_term_scroll_back and glyph_term_scroll navigate scrollback.

PTY Creation

The glyph_pty_open_and_spawn helper in libglyph opens a PTY pair via /dev/ptmx + TIOCGPTN + /dev/pts/N and spawns /bin/stsh using sys_spawn (syscall 514) to avoid the page-copy stall of forking Lumen’s large address space. The master fd is returned to the caller and stored in win->tag for polling.

ANSI Escape Handling

The glyph_term core (in libglyph) parses CSI sequences (ESC [ params final-byte) with a state machine:

State Meaning
0 Normal character processing
1 Got ESC, expecting [
2 Inside CSI, collecting parameters and final byte

Supported CSI sequences:

Sequence Code Function
ESC[nA CUU Cursor up n lines
ESC[nB CUD Cursor down n lines
ESC[nC CUF Cursor forward n columns
ESC[nD CUB Cursor back n columns
ESC[r;cH CUP Cursor position (row, col)
ESC[nJ ED Erase in display (0=below, 2=all)
ESC[nK EL Erase in line (0=right, 2=all)
ESC[...m SGR Set graphic rendition (colors, attrs)
ESC[?25h/l DECTCEM Show/hide cursor
ESC[?1049h/l   Alternate screen buffer enter/leave

SGR supports: reset (0), bold (1), underline (4), reverse (7), standard 8-color fg/bg (30-37, 40-47), bright fg/bg (90-97, 100-107), 256-color (38;5;N, 48;5;N), and truecolor (38;2;R;G;B, 48;2;R;G;B).

Text Selection and Clipboard

Mouse drag in the terminal client area initiates text selection. The selection is tracked as start/end (row, col) coordinates in the visible grid. Selected cells are drawn with a translucent blue highlight.

Copy (Alt+C or Ctrl+Shift+C) extracts selected text via terminal_copy_selection (which calls glyph_term_copy_selection), stripping trailing whitespace per row and joining rows with newlines. The clipboard is a simple 8KB static buffer in Lumen’s main.c. Paste (Alt+V or Ctrl+Shift+V) writes clipboard content directly to the focused window’s PTY master fd (win->tag).

Note: Clipboard copy/paste is only available for the in-process dropdown terminal. External /bin/terminal windows do not currently have access to Lumen’s clipboard — the protocol has no clipboard op in v1.

The dropdown terminal (Ctrl+Alt+T) is a chromeless, frosted-glass window spanning nearly the full screen width, positioned directly below the top bar. It starts hidden and toggles visibility. When shown, it steals focus; when hidden, focus returns to the previously focused window.

The dropdown is the only in-process terminal; regular terminal windows launched from the dock or Applications are external /bin/terminal processes.

Crossfade Transition

When Lumen starts, it captures the current framebuffer content (Bastion’s login screen), composites the desktop into the back buffer, then performs a 15-step crossfade over approximately 250ms:

for (int step = 0; step < 15; step++) {
    int alpha = 255 - (step * 255 / 14);
    int inv = 255 - alpha;
    for (size_t i = 0; i < npx; i++) {
        /* Per-pixel linear interpolation */
        uint32_t r = (((old >> 16) & 0xFF) * alpha +
                      ((new_px >> 16) & 0xFF) * inv) / 255;
        /* ... green, blue channels ... */
        fb[i] = (r << 16) | (g << 8) | b;
    }
    nanosleep(&ts, NULL);  /* 17ms per step */
}

Event Loop

The main loop structure (main.c):

for (;;) {
    0. Reap exited child processes (waitpid WNOHANG)
       - Prevents zombie accumulation from spawned apps
       - Without this, ~63 app opens exhaust MAX_PROCESSES

    1. Service external window clients (lumen_server_tick)
       - Accept new connections
       - Process pending opcodes from connected clients
       - Handle hung-up clients (comp_remove_window)

    2. Poll keyboard (stdin, raw, non-blocking)
       - Handle compositor shortcuts (Ctrl+Alt+T, Ctrl+Alt+I, Ctrl+Alt+L)
       - Forward keys/escape sequences to focused window
         * PTY-backed (tag >= 0): write raw bytes to PTY master fd
         * Proxy (on_key set): call on_key callback -> LUMEN_EV_KEY

    3. Poll mouse (/dev/mouse, batched)
       - Update context menu hover
       - Handle button press (menu, topbar, windows)
       - comp_handle_mouse() for drag, focus, widget dispatch

    4. Poll PTY masters for the dropdown terminal window
       - Read output, feed to glyph_term_feed()

    5. Update clock (~1/sec via frame counter)

    6. Composite + cursor update
       - Mouse-only: skip composite, just relocate cursor
       - Content change: cursor_hide -> comp_composite -> cursor_show

    7. Second PTY read pass (catch late shell output)

    8. Sleep 16ms if idle (no activity this frame)
}

Lock Screen

Screen locking is cooperative between Lumen and Bastion:

  1. Ctrl+Alt+L in Lumen sets s_input_frozen = 1 and sends SIGUSR1 to parent (Bastion)
  2. Bastion’s SIGUSR1 handler sets s_locked = 1, re-enters its own input loop showing the lock form
  3. On successful re-authentication, Bastion sends SIGUSR2 to Lumen
  4. Lumen’s SIGUSR2 handler clears s_input_frozen, resuming input processing

While frozen, Lumen discards all keyboard input but continues compositing normally.

Build

Lumen links against both libglyph.a (widget toolkit) and libcitadel.a (top bar):

SRCS = main.c cursor.c compositor.c terminal.c widget_test.c about.c \
       lumen_server.c

lumen.elf: $(OBJS) $(GLYPH) $(CITADEL)
    $(CC) $(CFLAGS) -o $@ $(OBJS) \
        -L../../lib/citadel -lcitadel \
        -L../../lib/glyph -lglyph

All sources are compiled with musl-gcc (-O2 -fno-pie -no-pie). The resulting lumen.elf is installed to /bin/lumen in the Aegis root filesystem.

Source Files

File Purpose
main.c Entry point, event loop, wallpaper loading, clipboard, context menu, invoke handler
compositor.c Window management, dirty-rect compositing, mouse/key dispatch
compositor.h compositor_t struct, constants (MAX_WINDOWS, MAX_DIRTY_RECTS)
cursor.c ARGB cursor sprite with save-under
terminal.c Dropdown terminal: thin wrapper over glyph_term (in libglyph); guards proxy-window priv pointer
widget_test.c Widget showcase window (tabs, buttons, checkboxes, etc.)
about.c “About Aegis” info window (system info from /proc, logo rendering)
lumen_server.c AF_UNIX server: accept/tick loop, proxy window lifecycle, LUMEN_OP_* dispatch, DnD brokering
lumen_server.h Server API + invoke handler registration