Vega ist der Compositor und Window Manager von Lyx OS. Er läuft als privilegierter Ring-3-Prozess und übernimmt alle Aufgaben, die klassisch einem Display-Server zufallen: GOP-Framebuffer beanspruchen, Fenster verwalten, Eingaben (Maus, Tastatur) verarbeiten und jeden Frame kompositionieren.
Lyx OS hat keinen Display-Server und kein Socket-Protokoll (kein X11, kein Wayland). Der Kernel stellt den Framebuffer und die Fenstertabelle direkt bereit; Vega liest sie ohne Umweg über IPC.
→ Übersicht · Kernel-Interna · Syscall-ABI
vega/
├── vega.lyx ← Compositor: Frame-Loop, Fensterzustand, Drag-Logik
└── bsys_vega.lyx ← Bridge-Unit: Syscall-Wrapper, Display-Backend,
VGA-Font, Fenster-Dekoration, Maus-Cursor
bsys_vega muss als erstes Unit importiert werden; Init() muss vor allen anderen Aufrufen stehen:
import bsys_vega;
fn main(): int64 {
Init(); // r3_sc_block binden + VGA-Font initialisieren
DisplayInit(); // GOP-Framebuffer beanspruchen + Back-Buffer allokieren
SysSetMouseBounds(scrw - 1, scrh - 1);
// ...
}
Der Frame-Loop ist der Kern von Vega. Er läuft mit ~100 Hz (PIT-Tick):
while (1 == 1) {
SysVsyncWait(); // auf nächsten PIT-Tick warten (~10 ms)
var mx: int64 := SysGetMouseX();
var my: int64 := SysGetMouseY();
var btn: int64 := SysGetMouseBtn();
handle_drag(mx, my, btn); // Titelleisten-Drag + Z-Order-Änderung
var ch: int64 := KbdPoll(); // non-blocking Tastatureingabe
if (ch != 0) { term_putch(ch); redraw_term_fb(); }
composite_frame(mx, my); // Desktop + Fenster + Cursor rendern
DisplayFlip(); // Back-Buffer → GOP-Framebuffer schreiben
}
Das Display-Backend abstrahiert den Bildspeicher über einen Double-Buffering-Ansatz: Alle Zeichenoperationen gehen in den Back-Buffer (RAM); DisplayFlip() überträgt ihn dann in den sichtbaren Bildspeicher. Der ist seit 2026-08 nicht mehr zwingend der vom UEFI geerbte GOP-Puffer — Lyx OS übernimmt den Bildschirm nach Möglichkeit selbst (sys_vbe_takeover, siehe unten) und fällt nur auf GOP zurück, wenn das scheitert.
var ok: int64 := DisplayInit(); // 1 = Erfolg, 0 = kein GOP-Framebuffer
var w: int64 := DisplayGetWidth();
var h: int64 := DisplayGetHeight();
var s: int64 := DisplayGetStride(); // Bytes pro Zeile (kann > w×4 sein)
| Funktion | Beschreibung |
|---|---|
DisplayFill(color) | Back-Buffer vollflächig füllen |
DisplayFillRect(x, y, w, h, color) | Rechteck im Back-Buffer füllen |
DisplayBlit(src, src_w, src_h, dst_x, dst_y) | Pixel-Buffer in Back-Buffer kopieren |
DisplayFlip() | Back-Buffer → GOP-Front-Buffer (8-Byte-Chunks, stride-aware) |
Alle Farben sind 32-Bit BGRA (Byte-Reihenfolge im Speicher: B, G, R, A):
con DESK_BG: int64 := 0xFF1A1A2E; // deep navy (Desktop-Hintergrund)
con WIN_BG_TERM: int64 := 0xFF0D1117; // Terminalfenster-Hintergrund
con WIN_FG: int64 := 0xFFCDD6F4; // primärer Text (helles Lavendel)
DisplayFlip() optimiert den Transfer: bei stride = w×4 werden alle Pixel als 8-Byte-Chunks (poke64) übertragen, was die MMIO-Transaktionen halbiert im Vergleich zu 4-Byte-Writes.
Bis 2026-08 hing der Compositor an dem Framebuffer, den die Firmware hinterlassen hatte: Adresse, Maße und Zeilenschrittweite kamen aus dem UEFI-GOP, der Bochs-VBE-Treiber war reiner Beobachter. Seit sys_vbe_takeover setzt Lyx OS den Modus selbst.
| Schritt | Was passiert |
|---|---|
| Eigener Modus | sys_vbe_takeover(w, h, bpp) (nr 106) setzt über Bochs-VBE genau die Maße des Firmware-Modus. Bewusst keine anderen: ein falscher Modus wäre ein schwarzer Schirm, und der Weg zurück über die Firmware steht dann nicht mehr offen |
| Bildspeicher aus BAR0 | Der Syscall bildet beide Bildhälften (virtuelle Höhe 2×h) in den Adressraum des Aufrufers ab und liefert die Adresse. DisplayInit() nimmt sie statt der aus sys_fb_map |
| Seitenwechsel | sys_vbe_flip(y) (nr 107) setzt den sichtbaren Bereich — ein Registerschreibzugriff statt einer Vollkopie. Vollkopien schreiben in die unsichtbare Hälfte und wechseln erst danach; der Betrachter sieht nie ein halb fertiges Bild. Teilkopien (Cursor, Damage) gehen weiter in die sichtbare Hälfte, denn sie ergänzen ein bestehendes Bild |
-1 und der GOP-Weg bleibt unverändert in Kraft. VBE_UEBERNEHMEN in vega/bsys_vega.lyx schaltet den Versuch mit einer Zeile ganz ab.DisplayCursorSave las 560 Punkte je Bild daraus — in VirtualBox je ein Austritt aus dem Gastmodus, comp stieg auf 115 ms und das Bild flackerte.gpu Bildschirm uebernommen: 1920×1080 Bildspeicher 0x80000000 virtuelle Hoehe 2184.
Bildschirmfotos der Statuszeile konnten die Frage nicht beantworten — die Streuung zwischen zwei Stichproben desselben Baus war größer als der Unterschied. Deshalb schreibt der Kernel die Verteilung selbst: FT_REPORT_EVERY in kernel/frametl.lyx lässt FrameTlSubmit alle N Bilder eine Zeile auf COM1 legen (Mittel, p50, p90, längstes Bild, verpasste Fristen; p50/p90 aus den letzten 128 Bildern). Last über QMP, feste Bahn, ohne Tastatureingabe, je drei Läufe:
Gilt für QEMU. VirtualBox lässt sich nicht skripten (kein mouseputevent); dort bleibt es beim Boot-Beleg, dass der neue Treiber die Ausgabe führt. FT_REPORT_EVERY steht im Dauerbau wieder auf 0 — der Bericht kostet Laufzeit.
Auf echter Intel-Hardware gibt es kein Bochs-VBE — dort bringt erst ein eigener Intel-Pfad denselben Gewinn. Vor dem Treiber steht aber die Diagnose: intel_diag() in kernel/gpu.lyx meldet beim Hochfahren je Pipe die rohen Werte conf/srcsz/stride/surf, dazu GGC (zugeteilter Grafikspeicher), den GTT-Schlüssel und die Größe des Sichtfensters. Kein einziger Schreibzugriff.
Zwei Fragen hängen daran: Stimmt die Deutung (die gpu-Zeile behauptet „Pipe 0, 1920×1080„ — auf einer Maschine, die hier nie lief, ist die rohe Zahl die einzige Tatsache)? Und passt ein zweiter Bildpuffer in den bereits zugeteilten Speicher? Dann geht der Seitenwechsel auf Intel wie in den VMs — nur PLANE_SURF umschalten, ohne Modeset. Sonst muss die GTT-Arbeit davor, denn PLANE_SURF erwartet eine GTT-Adresse, keine CPU-physische. Ein falsch gesetztes PLANE_SURF nimmt auf echter Hardware den Bildschirm — deshalb kein ungeprüfter Schreibzugriff.
Fenster werden vom Kernel in der Fenstertabelle (max. 32 Slots) verwaltet. Vega erzeugt Fenster über Syscalls; der physische Framebuffer jedes Fensters liegt im Kernel-Heap.
// Fenster anlegen (Client-Bereich ohne Titelleiste)
var wid: int64 := SysWinCreate(x, y, w, h, 0); // gibt win_id zurück oder -1
if (wid < 0) { return 1; }
// Framebuffer in den User-Adressraum mappen
var wfb: int64 := SysWinGetFb(wid); // user_virt oder -1
// In den Fenster-FB zeichnen (direkt per poke8/poke64)
FillWinFb(wfb, w, h, 0xFF0D1117); // Hintergrund füllen
DrawChar(wfb, w, 4, 4, 65, 0xFFCDD6F4, 0xFF0D1117); // 'A' bei (4,4)
| Konstante | Wert | Bedeutung |
|---|---|---|
VEGA_WIN_NORMAL | 0 | Standard-Fenster mit Dekoration |
VEGA_WIN_BORDERLESS | 1 | Kein Rahmen, keine Titelleiste |
VEGA_WIN_FULLSCREEN | 2 | Vollbild (übernimmt gesamten Screen) |
VEGA_WIN_RESIZABLE | 4 | Fenster kann vom User in der Größe geändert werden |
VEGA_WIN_HIDDEN | 8 | Fenster existiert, wird aber nicht gerendert |
Flags können kombiniert werden: VEGA_WIN_BORDERLESS | VEGA_WIN_RESIZABLE ergibt 5.
| Funktion | Beschreibung |
|---|---|
SysWinCreate(x, y, w, h, flags) | Neues Fenster; flags aus VEGA_WIN_*-Konstanten |
SysWinDestroy(win_id) | Fenster freigeben |
SysWinRaise(win_id) | Z-Order auf max+1 (in Vordergrund) |
SysWinMove(win_id, x, y) | Position verschieben |
SysWinResize(win_id, new_w, new_h) | Fenstergröße ändern |
SysWinGetFb(win_id) | Fenster-FB in User-Space mappen → user_virt |
SysWinGetTablePhys() | Physische Adresse der Fenstertabelle (Compositor-Direktzugriff) |
SysWinSetTitle(win_id, title_ptr) | Fenster-Titel setzen (WP27, nr=148) |
SysWinFindByIdx(idx) | win_id des Slots idx ermitteln (WP27, nr=149) |
SysWinGetTitle(win_id, buf) | Fenster-Titel lesen in buf (WP27, nr=150) |
SysWinGetGeom(win_id, out_buf) | Geometrie lesen: x,y,w,h als 4×int64 in out_buf (WP29, nr=151) |
SysWinGetPid(win_id) | PID des Fenster-Besitzers (WP29, nr=152) |
Vega liest die Fenstertabelle über ihre physische Adresse ohne Syscall-Overhead:
var tbl: int64 := SysWinGetTablePhys();
var i: int64 := 0;
while (i < 32) {
if (WinState(tbl, i) == 1) {
var x: int64 := WinX(tbl, i);
var y: int64 := WinY(tbl, i);
BlitWindow(WinPhys(tbl, i), WinW(tbl, i), WinH(tbl, i), x, y);
}
i := i + 1;
}
| Hilfsfunktion | Rückgabe |
|---|---|
WinSlot(tbl, idx) | Zeiger auf Slot (tbl + idx×104) |
WinId(tbl, idx) | win_id des Slots |
WinState(tbl, idx) | 0=frei, 1=aktiv |
WinX/Y/W/H/Z(tbl, idx) | Position, Größe, Z-Order |
WinPhys(tbl, idx) | Physische Adresse des Fenster-FB |
DrawDecoration() zeichnet Titelleiste, Rahmen und Schließen-Knopf in den Back-Buffer des Compositors (nicht in den Fenster-FB):
// focused: 1 = aktiv (dunkle Titelleiste), 0 = inaktiv (graue Titelleiste)
DrawDecoration(back_buf, display_width, cx, cy, cw, ch, "Terminal", 1);
| Konstante | Wert | Bedeutung |
|---|---|---|
DECO_TITLE_H | 28 | Titelleistenhöhe in Pixeln |
DECO_BAR_FOCUS | 0xFF2D3748 | Aktive Titelleiste (dunkles Schiefer) |
DECO_BAR_UNFOCUS | 0xFF718096 | Inaktive Titelleiste (grau) |
DECO_CLOSE | 0xFFE53E3E | Schließen-Knopf (rot) |
DECO_BORDER | 0xFF4A5568 | Fensterrahmen |
Die Titelleiste sitzt oberhalb des Client-Bereichs: [cy-28 .. cy). Für Drag-Hit-Tests ergibt sich daraus:
fn in_titlebar(mx, my, wx, wy, ww): int64 {
if (mx >= wx && mx < wx + ww && my >= wy - 28 && my < wy) { return 1; }
return 0;
}
Vega enthält einen eingebetteten VGA-8×16-Font für 96 druckbare ASCII-Zeichen (32–127). Der Font wird zur Laufzeit initialisiert (vga_font_init(), aufgerufen von Init()), da lyxc keine verketteten String-Literale als Initialisierungswert unterstützt.
// Einzelnes Zeichen an (px, py) in einen Pixel-Buffer zeichnen
DrawChar(buf, buf_w, px, py, ch, fg_color, bg_color);
// Null-terminierten String zeichnen; gibt x-Position nach letztem Zeichen zurück
var x_end: int64 := DrawString(buf, buf_w, px, py, "Hallo", WIN_FG, WIN_BG_TERM);
Jeder Glyph ist 8×16 Pixel; DrawChar clippt an der rechten Pufferkante. Zeichen außerhalb des druckbaren Bereichs werden als Leerzeichen (ASCII 32) dargestellt.
DrawCharToPhys und DrawStringToPhys sind Aliases, die explizit deutlich machen, dass der Buffer-Parameter eine physische Adresse ist.
DrawCursor() zeichnet einen 12×20-Pfeil-Sprite mit einfachem Drop-Shadow in den Back-Buffer — immer als letztes, sodass er über allem anderen liegt:
DrawCursor(back_buf, display_width, display_height, mouse_x, mouse_y);
Der Cursor-Sprite ist als Bitmask in bsys_vega.lyx eingebettet (arrow_rows, 40 Bytes für 20 Zeilen × 12 Bit). Der Drop-Shadow wird als schwarzes Pixel (+1,+1) hinter jedem Vordergrundpixel gesetzt, überschreibt aber keine Cursor-Pixel selbst.
SysSetMouseBounds(scrw - 1, scrh - 1); // Koordinatengrenzen setzen (einmalig)
var mx: int64 := SysGetMouseX(); // X-Koordinate
var my: int64 := SysGetMouseY(); // Y-Koordinate
var btn: int64 := SysGetMouseBtn(); // Tastenbits: VEGA_MBTN_*
| Konstante | Wert | Bedeutung |
|---|---|---|
VEGA_MBTN_LEFT | 1 | Linke Maustaste |
VEGA_MBTN_RIGHT | 2 | Rechte Maustaste |
VEGA_MBTN_MIDDLE | 4 | Mitteltaste (Rad-Click) |
if (btn & VEGA_MBTN_LEFT != 0) { /* Linksklick */ }
if (btn & VEGA_MBTN_RIGHT != 0) { /* Rechtsklick */ }
var ch: int64 := KbdPoll(); // 0 = kein Zeichen; sonst ASCII-Byte
KbdPoll() liest aus dem PS/2-Ringpuffer ohne zu blockieren (Syscall nr=131). Im Gegensatz zu sys_read(stdin) (nr=0) wartet er nicht auf Eingabe — geeignet für Frame-Loops.
Prozesse können sich gegenseitig Ereignisse (Typ + 3 Parameter) über einen kernel-seitigen Ringpuffer senden:
// Ereignis an PID 2 senden
SysEventSend(2, EV_KEY_PRESS, 65, 0, 0);
// Eigenes Postfach prüfen (non-blocking)
var ev_buf: int64 := mmap(0, 32, 3, 34, -1, 0); // 4 × int64
var ok: int64 := SysEventRecv(ev_buf);
if (ok == 0) {
var ev_type: int64 := peek64(ev_buf);
var ev_a: int64 := peek64(ev_buf + 8);
}
Der Ring fasst pro PID 16 Ereignisse (32 Bytes/Ereignis). PIDs ≥ 8 werden ignoriert. Ist der Ring voll, gehen neue Ereignisse verloren (kein Backpressure).
var shm_id: int64 := SysShmCreate(4096); // SHM-Region anlegen (shm_id oder -1)
var uva: int64 := SysShmMap(shm_id); // in User-Space mappen
SysShmUnmap(shm_id); // Referenzzähler dekrementieren
// PutCh-Ausgabe von PID 3 in SHM-Ringpuffer umleiten
SysSetConsole(3, shm_id);
// Routing abschalten:
SysSetConsole(3, -1);
std.lyxos.vega (src/std/lyxos/vega.lyx, LX-WP17) ist die öffentliche Standard-Library-Unit für Ring-3-Anwendungen. Sie bietet benannte Konstanten und Vega*-Wrapper um alle Vega-Syscalls — ohne bsys_vega zu importieren.
import std.lyxos.vega;
import std.alloc;
fn main(): int64 {
// Fenster anlegen
var wid: int64 := VegaWinCreate(100, 100, 640, 480, VEGA_WIN_RESIZABLE);
if (wid == 0) { return 1; }
// Framebuffer mappen
var fb: int64 := VegaWinGetFb(wid);
// Mausgrenzen setzen
VegaSetMouseBounds(1919, 1079);
// Frame-Loop
while (1 == 1) {
VegaVsyncWait();
var mx: int64 := VegaGetMouseX();
var my: int64 := VegaGetMouseY();
var ch: int64 := VegaKbdPoll(); // 0 = kein Zeichen
// Ereignisse empfangen
var evBuf: int64 := alloc(32);
var n: int64 := VegaEventRecv(evBuf);
if (n > 0) {
var t: int64 := peek64(evBuf); // VEGA_EVT_*
if (t == VEGA_EVT_WIN_CLOSE) { break; }
}
free(evBuf, 32);
}
VegaWinDestroy(wid);
return 0;
}
Window-Flags (sys_win_create):
| Konstante | Wert |
|---|---|
VEGA_WIN_NORMAL | 0 |
VEGA_WIN_BORDERLESS | 1 |
VEGA_WIN_FULLSCREEN | 2 |
VEGA_WIN_RESIZABLE | 4 |
VEGA_WIN_HIDDEN | 8 |
Ereignis-Typen (sys_event_send / sys_event_recv):
| Konstante | Wert | Bedeutung |
|---|---|---|
VEGA_EVT_KEY_DOWN | 1 | Taste gedrückt |
VEGA_EVT_KEY_UP | 2 | Taste losgelassen |
VEGA_EVT_MOUSE_MOVE | 3 | Maus bewegt |
VEGA_EVT_MOUSE_BTN | 4 | Maustaste gedrückt/losgelassen |
VEGA_EVT_WIN_CLOSE | 5 | Schließen-Knopf geklickt |
VEGA_EVT_WIN_RESIZE | 6 | Fenster in der Größe geändert |
VEGA_EVT_WIN_FOCUS | 7 | Fokus gewonnen / verloren |
Maustastenbits (sys_get_mouse_btn):
| Konstante | Wert |
|---|---|
VEGA_MBTN_LEFT | 1 |
VEGA_MBTN_RIGHT | 2 |
VEGA_MBTN_MIDDLE | 4 |
Fenster-Verwaltung:
| Signatur | Beschreibung |
|---|---|
VegaWinCreate(x, y, w, h, flags): int64 | Fenster anlegen; gibt win_id zurück (0 = Fehler) |
VegaWinDestroy(winId): int64 | Fenster freigeben |
VegaWinRaise(winId): int64 | In Vordergrund bringen |
VegaWinMove(winId, x, y): int64 | Fenster verschieben |
VegaWinResize(winId, newW, newH): int64 | Fenstergröße ändern |
VegaWinGetFb(winId): int64 | Fenster-Framebuffer → UVA (0 = Fehler) |
VegaWinGetTablePhys(): int64 | Physische Adresse der Fenstertabelle |
VegaWinSetTitle(winId, titlePtr): int64 | Fenster-Titel setzen (WP27) |
VegaWinFindByIdx(idx): int64 | win_id für Slot-Index ermitteln (WP27) |
VegaWinGetTitle(winId, buf): int64 | Fenster-Titel lesen (WP27) |
VegaWinGetGeom(winId, outBuf): int64 | Geometrie lesen: x,y,w,h in outBuf (4×int64) (WP29) |
VegaWinGetPid(winId): int64 | PID des Fenster-Besitzers (WP29) |
Display / Framebuffer:
| Signatur | Beschreibung |
|---|---|
VegaVsyncWait(): int64 | Blockiert bis zum nächsten VSync/PIT-Tick |
VegaFbMapUser(fbPhys, fbW, fbH, stride): int64 | Physischen Framebuffer in User-Adressraum mappen → UVA |
Shared Memory:
| Signatur | Beschreibung |
|---|---|
VegaShmCreate(size): int64 | SHM-Region anlegen; gibt shm_id zurück (0 = Fehler) |
VegaShmMap(shmId): int64 | In User-Space mappen → UVA (0 = Fehler) |
VegaShmUnmap(shmId): int64 | Referenzzähler dekrementieren |
Ereignisse:
| Signatur | Beschreibung |
|---|---|
VegaEventSend(pid, evtType, a, b): int64 | Ereignis an PID senden; evtType aus VEGA_EVT_* |
VegaEventRecv(outBuf): int64 | Eigenes Postfach prüfen; gibt Anzahl geschriebener Ereignisse zurück |
VegaSetConsole(pid, shmId): int64 | PutCh-Ausgabe von PID in SHM-Ringpuffer umleiten (shmId=-1 zum Deaktivieren) |
PS/2-Maus:
| Signatur | Beschreibung |
|---|---|
VegaGetMouseX(): int64 | X-Koordinate |
VegaGetMouseY(): int64 | Y-Koordinate |
VegaGetMouseBtn(): int64 | Tastenbits (VEGA_MBTN_*) |
VegaSetMouseBounds(maxX, maxY): int64 | Koordinatengrenzen setzen |
Tastatur:
| Signatur | Beschreibung |
|---|---|
VegaKbdPoll(): int64 | Non-blocking; 0 = kein Zeichen, sonst Scancode |
Zeit:
| Signatur | Beschreibung |
|---|---|
VegaTimeNs(): int64 | Nanosekunden seit Systemstart |
| Datei | Inhalt |
|---|---|
vega/vega.lyx | Compositor-Hauptprogramm: Frame-Loop, Fenstergeometrie, Drag-Logik, Kompositionierung |
vega/bsys_vega.lyx | Bridge-Unit: Syscall-Wrapper (WP17/WP17b), Display-Backend, VGA-Font, Dekoration, Maus-Cursor, Fenster-Tabellenhelfer |
vega/bsys_vega.lyu | Vorkompilierte Unit (lyxc-Output) |
vega/vega.elf | Fertig gelinktes ELF64-Binary |
src/std/lyxos/vega.lyx | Standard-Library-Unit (LX-WP17): öffentliche Vega-API für Ring-3-Anwendungen |
Letzte Aktualisierung: 2026-08-29 — Abschnitt „Bildschirm übernehmen statt erben“ ergänzt (Syscalls 106/107, Frametime-Messlauf).
Vorherige Aktualisierung: 2026-06-25