-
-
Notifications
You must be signed in to change notification settings - Fork 5
HOWTO Add a GUI application
How to add a windowed application to the NyxOS desktop. Built-in apps are kernel windows: one *_win.c / *_win.h pair each, a per-instance context struct, and a set of callbacks the compositor invokes.
The worked example is a stopwatch with a display, two buttons and a running clock.
See also: GUI-Subsystem, Desktop-Applications, Kernel-Data-Structures, Building
The compositor owns the window frame, the title bar, dragging, resizing and z-order. An application supplies a draw function and whichever callbacks it needs.
FILE — kernel/gui/core/compositor.h (the callback set)
window_draw_fn draw;
void (*on_key)(struct window* win, int key);
void (*on_click)(struct window* win, int mx, int my, int btn);
void (*on_pressed)(struct window* win, int mx, int my, int btn);
void (*on_mousemove)(struct window* win, int mx, int my, int btns);
int (*on_tick)(struct window* win);
void* reserved;| Callback | When | Notes |
|---|---|---|
draw |
Every repaint | Receives the client rect: (cx, cy, cw, ch)
|
on_click |
Button release inside the client area | Coordinates are screen-absolute |
on_pressed |
Button press | Use for drag-style interaction |
on_mousemove |
Pointer moves over the window |
btns is the current button mask |
on_key |
Key while the window is focused | |
on_tick |
Periodic, ~30 fps |
Return 1 if something changed and needs a repaint, 0 if idle |
reserved |
— | Your per-instance context pointer |
Important
reserved is how per-window state is stored. Never use a static variable for application state — a second window of the same app would share it.
Warning
on_tick returning 1 every call forces a full recomposite at 30 fps and will visibly slow the desktop. Return 1 only when the window's contents actually changed.
FILE — kernel/gui/apps/stopwatch_win.h
#ifndef STOPWATCH_WIN_HDR
#define STOPWATCH_WIN_HDR
#include "kernel.h"
#include "compositor.h"
#define SW_WIN_W 220
#define SW_WIN_H 120
typedef struct {
uint64_t start_tick; /* tick_count when started, 0 = stopped */
uint64_t accumulated; /* ticks banked from previous runs */
int running;
char display[16];
} stopwatch_ctx_t;
stopwatch_ctx_t* stopwatch_create_ctx(void);
void stopwatch_win_draw(window_t* win, int cx, int cy, uint32_t cw, uint32_t ch);
void stopwatch_win_click(window_t* win, int mx, int my, int btn);
int stopwatch_win_tick(window_t* win);
#endifGeometry constants belong in the header so the launcher can size the window from them, exactly as calc_win.h does with CALC_WIN_W/CALC_WIN_H.
FILE — kernel/gui/apps/stopwatch_win.c
#include "theme.h"
#include "kernel.h"
#include "compositor.h"
#include "stopwatch_win.h"
#include "font.h"
#define BTN_W 80
#define BTN_H 28
#define PAD 12
stopwatch_ctx_t* stopwatch_create_ctx(void) {
stopwatch_ctx_t* c = (stopwatch_ctx_t*)kmalloc(sizeof(stopwatch_ctx_t));
if (!c) return 0;
memset(c, 0, sizeof(*c));
strncpy(c->display, "0.0", sizeof(c->display) - 1);
return c;
}
static uint64_t elapsed_ticks(stopwatch_ctx_t* c) {
return c->running ? c->accumulated + (tick_count - c->start_tick)
: c->accumulated;
}
void stopwatch_win_draw(window_t* win, int cx, int cy, uint32_t cw, uint32_t ch) {
stopwatch_ctx_t* c = (stopwatch_ctx_t*)win->reserved;
if (!c) return;
fb_fill_rect(cx, cy, cw, ch, THEME_WINDOW_BG);
uint64_t ms = elapsed_ticks(c); /* 1000 Hz => ticks are ms */
snprintf(c->display, sizeof(c->display), "%llu.%llu", ms / 1000, (ms % 1000) / 100);
font_draw_string(cx + PAD, cy + PAD, c->display, THEME_TEXT, THEME_WINDOW_BG);
int by = cy + ch - BTN_H - PAD;
fb_fill_rect(cx + PAD, by, BTN_W, BTN_H, THEME_ACCENT);
font_draw_string(cx + PAD + 16, by + 8,
c->running ? "Stop" : "Start", THEME_ON_ACCENT, THEME_ACCENT);
fb_fill_rect(cx + PAD * 2 + BTN_W, by, BTN_W, BTN_H, THEME_ACCENT_DIM);
font_draw_string(cx + PAD * 2 + BTN_W + 16, by + 8,
"Reset", THEME_ON_ACCENT, THEME_ACCENT_DIM);
}
void stopwatch_win_click(window_t* win, int mx, int my, int btn) {
stopwatch_ctx_t* c = (stopwatch_ctx_t*)win->reserved;
if (!c || btn != 1) return;
int cx = win->x, cy = win->y; /* client origin; see note below */
int by = cy + (int)win->h - BTN_H - PAD;
if (my < by || my >= by + BTN_H) return;
if (mx >= cx + PAD && mx < cx + PAD + BTN_W) {
if (c->running) { c->accumulated = elapsed_ticks(c); c->running = 0; }
else { c->start_tick = tick_count; c->running = 1; }
} else if (mx >= cx + PAD * 2 + BTN_W && mx < cx + PAD * 2 + BTN_W * 2) {
c->accumulated = 0;
c->start_tick = tick_count;
}
}
int stopwatch_win_tick(window_t* win) {
stopwatch_ctx_t* c = (stopwatch_ctx_t*)win->reserved;
return (c && c->running) ? 1 : 0; /* repaint only while running */
}| Rule | Why |
|---|---|
Colours come from theme.h roles |
A literal fb_rgb(…) cannot be re-themed. See GUI-Subsystem
|
| Lay out on the 1024×768 design grid | The compositor scales to the live framebuffer; deriving from fb_get_width() applies the scale twice |
| Clip drawing to the client rect | Drawing outside it corrupts neighbouring windows |
| No floating point | The kernel is built -mno-sse; use integer or fixed-point maths |
Allocate context with kmalloc
|
And memset it — kmalloc does not zero |
FILE — kernel/gui/core/compositor.c
void launch_stopwatch(void) {
window_t* w = window_create(340, 200, SW_WIN_W, SW_WIN_H,
"Stopwatch", stopwatch_win_draw);
if (!w) return;
w->reserved = stopwatch_create_ctx();
if (w->reserved) {
w->on_click = stopwatch_win_click;
w->on_tick = stopwatch_win_tick;
}
}window_create returns NULL when MAX_WINDOWS (32) is reached — always check.
This is the same shape as the existing launch_minesweeper():
FILE — kernel/gui/core/compositor.c (existing code)
void launch_minesweeper(void) {
window_t* mwin = window_create(320, 180, MS_WIN_W, MS_WIN_H,
"Minesweeper", minesweeper_win_draw);
if (!mwin) return;
mwin->reserved = minesweeper_create_ctx();
if (mwin->reserved) {
mwin->on_click = minesweeper_win_click;
mwin->on_key = minesweeper_win_key;
}
}Add a case to the action switch in compositor.c:
case 16: // Stopwatch
launch_stopwatch();
break;Two parallel arrays hold the icon set:
FILE — kernel/gui/core/compositor.c
#define NUM_DESKTOP_ICONS 11 /* was 10 */
static const char* desktop_icon_names[] = {
"Files", "Terminal", "Editor", "Viewer", "Settings",
"Paint", "Sounds", "Calc", "Games", "Selene", "Timer"
};
static int desktop_icon_actions[] = {0, 3, 1, 2, 4, 7, 8, 11, 14, 15, 16};Important
NUM_DESKTOP_ICONS, desktop_icon_names[] and desktop_icon_actions[] must all agree. A mismatch reads past the end of an array and draws garbage — or faults.
Icons reflow into a grid automatically, so no coordinates need updating.
{"stopwatch", cmd_stopwatch, "Open the Stopwatch app", false},See HOWTO-Add-a-shell-command.
FILE — kernel/Makefile
OBJS_KERNEL = … stopwatch_win.o …Header dependencies are tracked automatically by -MMD.
CODE — Rebuild and boot the desktop
host $ make -C kernel
host $ ./run.ps1Check: the icon appears and is labelled; clicking opens the window; buttons respond; the clock advances only while running; the window drags, resizes, minimises and closes; a second instance keeps its own state; the desktop stays responsive.
window_destroy frees the window but does not know how to free reserved. If your context owns further allocations — a decoded image, a file buffer — free them where the app is torn down, following how Selene-Browser releases decoded images when a tab closes.
- Header defines the context struct and window geometry
-
create_ctx()useskmalloc+memset, and returnsNULLon failure - All per-window state lives in
reserved, never instatic - Colours use
theme.hroles - Layout uses the 1024×768 design grid
- Drawing stays inside the client rect
- No floating point
-
on_tickreturns1only when something changed -
window_createresult checked forNULL - Icon arrays and
NUM_DESKTOP_ICONSall updated together -
.oadded toOBJS_KERNEL - Builds with zero warnings
- Desktop-Applications page updated in this wiki
| Symptom | Cause | Fix |
|---|---|---|
| Window opens blank |
draw not passed to window_create
|
Pass it as the last argument |
| Clicks do nothing |
on_click assigned before reserved was checked, or the ctx allocation failed |
Assign callbacks inside the if (w->reserved) block |
| Content drawn in the wrong place | Layout derived from fb_get_width()
|
Use the 1024×768 design grid |
| Drawing appears over other windows | Drawing outside the client rect | Clip to (cx, cy, cw, ch)
|
| Desktop becomes sluggish |
on_tick always returns 1
|
Return 0 when idle |
| Two windows share state | State in a static variable |
Move it into the context struct |
| Garbage icons, or a fault at startup | Icon arrays out of sync with NUM_DESKTOP_ICONS
|
Update all three together |
Link error undefined reference to stopwatch_win_draw
|
.o missing from OBJS_KERNEL
|
Add it |
| Build error mentioning SSE | Floating point in kernel code | Use integer maths |
- GUI-Subsystem — the compositor, the theme, and the framebuffer primitives
- Desktop-Applications — every existing app
-
Kernel-Data-Structures —
window_tfield by field - HOWTO-Add-a-shell-command — to add a launcher command
-
Bochs VBE extensions — the display interface behind
vbe.c
NyxOS v6.4.363 · GPL v2 · GitHub · uselessalter on Discord · nyxos@inbox.lv
NyxOS Wiki
Getting started
Kernel
Storage & network
Graphics & apps
Userspace
HOWTO
- HOWTO-Add-a-system-call
- HOWTO-Write-a-userspace-program
- HOWTO-Add-a-shell-command
- HOWTO-Add-a-GUI-application
Reference
- Syscall-Reference
- Command-Reference
- Hardware-Reference
- Format-Reference
- Kernel-Data-Structures
- Source-Tree-Reference
Project