Lumen Compositor
Direct-framebuffer window compositor with dirty-rect tracking, frosted glass compositing, and integrated terminal emulator
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 theglyph_termemulator 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:
- Box blur the backbuffer region under the element (
draw_box_blur, radius 10) - Color tint via alpha-blended rectangle (
draw_blend_rect) - 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:
- Before drawing: save the framebuffer pixels under the cursor position to
s_save[] - Alpha-blend the cursor sprite onto the framebuffer
- 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):
- Context menu hit testing (click on menu item, or outside to close)
- Top bar “Aegis” click (opens/closes context menu)
- Desktop selection box (drag on empty space)
- Content drag (text selection in terminal)
- Titlebar drag (window move)
- Window close button (traffic-light red circle)
- Window focus change + raise
- 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 callsglyph_window_destroy(win)internally; never callglyph_window_destroyagain afterward in proxy hangup paths or you double-free.comp_remove_windowclearscomp->focused,comp->drag_win, andcomp->content_drag_winif any of them pointed at the removed window. Any other code holding aglyph_window_t *outside thewindows[]array must clear it on add/raise.- For proxy windows,
pw->shared(the memfd map) ismunmapped 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.tagis initialized to-1(sentinel). Lumen usestag >= 0to mean “deliver via direct PTY write”. Default 0 fromcallocpreviously 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.
Dropdown Terminal
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:
Ctrl+Alt+Lin Lumen setss_input_frozen = 1and sendsSIGUSR1to parent (Bastion)- Bastion’s SIGUSR1 handler sets
s_locked = 1, re-enters its own input loop showing the lock form - On successful re-authentication, Bastion sends
SIGUSR2to Lumen - 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 |