Vega – Fensterverwaltungssystem

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


1. Architektur

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);
    // ...
}


2. Frame-Loop

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
}


3. Display-Backend

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.

Initialisierung

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)

Zeichenoperationen

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)

Farbformat

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.

Bildschirm übernehmen statt erben (Bochs-VBE)

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
  • EFI bleibt Rückfall. Scheitert ein Schritt — kein VBE, kein Bildspeicher in BAR0, kein Platz für zwei Bilder —, liefert der Syscall -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.
  • Der Rückpuffer bleibt im Arbeitsspeicher. Das ist der Unterschied zum zurückgenommenen Versuch mit direktem Zeichnen (V0.0.2AT): dort lag er im Bildspeicher, und DisplayCursorSave las 560 Punkte je Bild daraus — in VirtualBox je ein Austritt aus dem Gastmodus, comp stieg auf 115 ms und das Bild flackerte.
  • Der Kernel schweigt danach. Mit der Übernahme stellt der Kernel seine eigene Bildschirmausgabe ein, sonst stünde Text mitten im Desktop. Die Übernahme meldet er einmal, mit Adresse und virtueller Höhe — ein übernommener Bildschirm sieht sonst genauso aus wie ein geerbter: gpu Bildschirm uebernommen: 1920×1080 Bildspeicher 0x80000000 virtuelle Hoehe 2184.

Was es bringt (gemessen, QEMU)

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:

  • ~6 % kürzere Bilddauer und 8 % weniger verpasste Fristen gegenüber dem EFI-Weg; die Wertebereiche der Läufe überschneiden sich nicht.
  • Wichtiger als der Gewinn ist die Streuung: VBE schwankt über drei Läufe um 6 %, EFI um 21 %.
  • p50 ist mit ~8 ms praktisch gleich — der Unterschied sitzt in den langen Bildern, nicht im Normalfall. Genau dort fällt das Kopieren in den Bildspeicher ins Gewicht.

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.

Intel-Grafik: erst lesen, dann schreiben

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.


4. Fenster-API

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 und befüllen

// 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)

Window-Flags (VEGA_WIN_*)

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.

Fenster-Verwaltung

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)

Fenstertabelle direkt lesen (Compositor)

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

5. Fenster-Dekoration

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;
}


6. VGA-Font

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.


7. Maus-Cursor

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.


8. Eingabe

PS/2-Maus

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 */ }

Tastatur (non-blocking)

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.


9. Ereignis-Ring

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).


10. SHM und Console-Routing

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);


11. std.lyxos.vega — Standardbibliothek für Ring-3-Anwendungen

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;
}

Konstanten

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

Funktionen

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

12. Quelldateien

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