WShell führt Extensions aus — dieselben, die auch in VSCode laufen. Dieser Artikel zeigt Schritt für Schritt, wie Sie Ihre erste Extension erstellen, in die WShell-Oberfläche integrieren, ein Master-Detail-Formular entwerfen und über verschlüsselte WebAPI-Aufrufe mit einem Backend kommunizieren.
Stellen Sie sicher, dass Sie eine laufende WShell haben (Installationsanleitung). Für die Entwicklung brauchen Sie außerdem:
wvds.fpc-solution-manager) — Projekt anlegen, bauen, ausführenwvds.fpc-pas2js-studio) — visueller Formular-Designer, Code-Generator, Live-VorschauOhne diese beiden Extensions legen Sie Manifest, Compiler-Flags und Formularcode von Hand an — fehlerträchtig und bei jedem neuen Projekt dieselbe Arbeit. Der Solution Manager übernimmt die Projektstruktur: Er kennt die richtigen pas2js-Flags, erzeugt ein konsistentes Build-Skript und bindet das Projekt direkt in die VSCode-Toolchain ein. pas2js Studio ergänzt den visuellen Teil: Formulare entstehen per Drag & Drop statt durch manuelles JSON-Editieren, und der Code-Generator hält Felder, FindComponent-Aufrufe und Event-Bindungen automatisch synchron. Das Ergebnis ist ein reproduzierbarer Workflow, der Einarbeitungszeit verkürzt, Flüchtigkeitsfehler eliminiert und den Weg vom Prototyp zur fertigen Extension auf Minuten statt Stunden reduziert.
Der FPC Solution Manager erzeugt die komplette Projektstruktur für Sie — Manifest, Einstiegspunkt, Build-Skript. Sie müssen nichts von Hand anlegen.
Öffnen Sie die Befehlspalette (Ctrl+Shift+P) und wählen Sie:
FPCSE: Start Project Wizard
Der Wizard führt Sie durch fünf Schritte:
Für eine WShell Extension wählen Sie den Typ „VSCode Extension“ unter der Kategorie pas2js Web.
Nach Abschluss haben Sie ein vollständiges Projekt:
phonebook/
package.json <-- Manifest (vom Wizard ausgefüllt)
src-pas/
extension.pas <-- Einstiegspunkt mit activate/deactivate
dist/
extension.js <-- Wird beim ersten Build erzeugt
build.ps1 <-- Build-Skript
Der Solution Manager registriert das Projekt automatisch in der Projektbaumansicht. Von dort können Sie bauen (Ctrl+Shift+B), ausführen (F5) und Einstellungen ändern (F4).
Jetzt wird es praktisch. Wir entwerfen ein einfaches Telefonbuch mit zwei Bereichen:
Öffnen Sie die Befehlspalette und wählen Sie:
pas2js: New Web Form
Geben Sie als Namen PhonebookForm ein. Das erzeugt zwei Dateien:
PhonebookForm.wfm — die Formularbeschreibung (JSON)PhonebookForm.pas — die Pascal-Unit mit der Formularklasse
Öffnen Sie PhonebookForm.wfm — der visuelle Designer startet automatisch. Er zeigt drei Bereiche:
Bauen Sie das Layout Schritt für Schritt auf:
Schritt 1 — Hauptcontainer: Das Formular hat bereits ein leeres flexColumn-Layout. Ändern Sie es auf flexRow (im Inspektor unter Layout > Typ). Das teilt das Formular in eine linke und rechte Hälfte.
Schritt 2 — Master-Panel: Ziehen Sie ein TWPanel aus der Palette auf das Formular. Benennen Sie es MasterPanel. Setzen Sie:
| Eigenschaft | Wert |
|---|---|
| Layout-Typ | flexColumn |
| Breite | 300 (feste Breite in Pixel) |
| Höhe | 100% |
Schritt 3 — Suchfeld: Ziehen Sie ein TWEdit in das MasterPanel. Benennen Sie es SearchEdit. Setzen Sie:
| Eigenschaft | Wert |
|---|---|
| Placeholder | Kontakt suchen… |
| Breite | 100% |
| Margin | sm |
Schritt 4 — Kontaktliste: Ziehen Sie eine TWListBox unter das Suchfeld. Benennen Sie sie ContactList. Setzen Sie:
| Eigenschaft | Wert |
|---|---|
| Breite | 100% |
| FlexGrow | 1 (füllt den restlichen Platz) |
| Margin | sm |
Schritt 5 — Detail-Panel: Ziehen Sie ein zweites TWPanel rechts neben das MasterPanel. Benennen Sie es DetailPanel. Setzen Sie:
| Eigenschaft | Wert |
|---|---|
| Layout-Typ | flexColumn |
| FlexGrow | 1 (füllt den gesamten Restplatz) |
| Margin | md |
Schritt 6 — Detail-Felder: Ziehen Sie in das DetailPanel drei TWEdit-Felder untereinander:
| Name | Placeholder |
|---|---|
NameEdit | Vorname Nachname |
PhoneEdit | +43 1 234 5678 |
EmailEdit | name@example.com |
Schritt 7 — Speichern-Button: Ziehen Sie einen TWButton unter die drei Felder. Benennen Sie ihn SaveButton, Text: „Speichern“.
Der Designer speichert alles als JSON. Sie können die WFM-Datei jederzeit auch direkt als JSON bearbeiten — der Designer und der Text-Editor bleiben synchron.
Das Formular sieht jetzt gut aus, aber es tut noch nichts. Jetzt verbinden wir Benutzeraktionen mit Pascal-Code.
Klicken Sie im Designer auf ContactList. Im rechten Inspektor wechseln Sie zum Tab Events. Dort sehen Sie:
| Event | Handler |
|---|---|
OnClick | (leer) |
OnDblClick | (leer) |
Doppelklicken Sie auf die leere Zeile neben OnClick. Der Designer:
ContactListClick).pas-Datei einWiederholen Sie das für:
| Komponente | Event | Erzeugter Handler |
|---|---|---|
SearchEdit | OnChange | SearchEditChange |
SaveButton | OnClick | SaveButtonClick |
Der Designer pflegt automatisch zwei markierte Bereiche in Ihrer Unit: Felder (zwischen { WFM-AUTO-BEGIN } und { WFM-AUTO-END }) sowie FindComponent-Aufrufe und Event-Bindungen in AfterCreate. Alles außerhalb dieser Marker bleibt bei jeder Regenerierung erhalten — Sie können beliebig eigenen Code ergänzen, ohne dass der Designer ihn überschreibt.
Jetzt füllen Sie die leeren Handler mit Logik. ContactListClick zeigt die Detail-Felder des ausgewählten Kontakts, SearchEditChange filtert die Liste live, und SaveButtonClick schreibt Änderungen zurück ins Array.
Das Telefonbuch braucht Kontaktdaten. Diese kommen aus dem Shell DataBridge (Schritt 8) oder — zum Testen — aus einem lokalen Array.
Ergänzen Sie in der Unit ein Record und ein Array:
type TContact = record Name: string; Phone: string; Email: string; end; var FContacts: array of TContact;
Fügen Sie eine Lademethode hinzu und rufen Sie sie in AfterCreate auf (nach den WFM-Markern):
procedure TPhonebookForm.LoadContacts; begin SetLength(FContacts, 3); FContacts[0].Name := 'Anna Huber'; FContacts[0].Phone := '+43 1 234 5678'; FContacts[0].Email := 'anna@example.com'; FContacts[1].Name := 'Max Bauer'; FContacts[1].Phone := '+43 1 876 5432'; FContacts[1].Email := 'max@example.com'; FContacts[2].Name := 'Lisa Gruber'; FContacts[2].Phone := '+43 1 555 0000'; FContacts[2].Email := 'lisa@example.com'; FContactList.Items.Clear; for var I := 0 to High(FContacts) do FContactList.Items.Add(FContacts[I].Name); end;
Wenn die Extension in WShell läuft und ein Daten-Provider konfiguriert ist, ersetzen Sie LoadContacts durch eine DataBridge-Abfrage. Der DataBridge kümmert sich um die Datenbankverbindung — Ihre Extension sendet nur die SQL-Abfrage und empfängt ein JSON-Ergebnis.
Drücken Sie Ctrl+Shift+B oder klicken Sie im Projektbaum auf Build. Der Solution Manager:
-Tnodejs -Jc -Jirtl.js)Ziel: 0 Fehler, 0 Warnungen, 0 Hinweise.
Klicken Sie im Projektbaum mit der rechten Maustaste auf Ihr Projekt und wählen Sie Extension Build → Install Extension. Der Solution Manager baut die Extension, erzeugt das VSIX-Paket und installiert es direkt in die lokale WShell-Instanz. Nach einem Neustart der WShell erscheint Ihr Telefonbuch als neuer Tab.
Rechtsklick auf das Projekt → Extension Build → Package VSIX. Der Solution Manager:
binaries/vsix/{name}-{version}.vsix
Die fertige .vsix-Datei können Sie an andere Entwickler weitergeben oder über den Extension-Manager installieren. Zum Veröffentlichen im Marketplace verwenden Sie Extension Build → Publish Extension.
Falls Sie den Solution Manager nicht verwenden oder den Build in eine CI/CD-Pipeline einbinden möchten, können Sie eine eigene build.ps1 schreiben. Das folgende Unter-Howto zeigt Ihnen den Aufbau Schritt für Schritt.
Ab hier geht es um die Integration Ihrer fertigen Extension in die WShell-Oberfläche. Die WShell hat eine Menüleiste (Datei, Bearbeiten, Ansicht, …). Ihre Extension kann Einträge zu bestehenden Menüs hinzufügen oder komplett neue Menüs anlegen.
In der package.json tragen Sie unter contributes.menus ein, wo Ihr Befehl erscheinen soll. Der Schlüssel ist der Menü-Ort, der Wert ist eine Liste von Einträgen.
{
"contributes": {
"commands": [
{
"command": "myext.export",
"title": "Daten exportieren",
"category": "Meine Extension"
}
],
"menus": {
"menuBar/file": [
{
"command": "myext.export",
"group": "export",
"order": 1
}
]
}
}
}
Dieser Eintrag fügt „Daten exportieren“ ins Datei-Menü ein.
So funktioniert das Zusammenführen:
menus-Beiträge aus allen ExtensionsmenuBar/file bedeutet: suche das bestehende Datei-MenüUm ein eigenes Menü (z.B. „Werkzeuge“) hinzuzufügen, verwenden Sie Untermenüs:
{
"contributes": {
"submenus": [
{ "id": "myext.tools", "label": "Werkzeuge" }
],
"menus": {
"menuBar": [
{ "submenu": "myext.tools", "order": 50 }
],
"myext.tools": [
{ "command": "myext.export", "group": "io", "order": 1 },
{ "command": "myext.import", "group": "io", "order": 2 }
]
}
}
}
Die WShell verarbeitet das in zwei Durchläufen:
menuBar → legt das neue Top-Level-Menü „Werkzeuge“ anmyext.tools → füllt es mit Ihren Befehlen| Ort | Wo er erscheint |
|---|---|
menuBar | Neues Top-Level-Menü |
menuBar/file | Im Datei-Menü |
commandPalette | In der Befehlspalette (Ctrl+Shift+P) |
view/title | Als Aktionsbutton in der Kopfzeile einer Ansicht |
view/item/context | Im Rechtsklick-Menü auf Baumeinträge |
Die QuickBar ist die Reihe von Symbolbuttons in der Titelleiste der WShell — Schnellzugriffe, die immer sichtbar sind.
Im Gegensatz zu Menüs werden QuickBar-Buttons nicht über die package.json definiert, sondern zur Laufzeit bereitgestellt. Der Ablauf:
function TMyTab.GetQuickCommands: TWvdSQuickCommands; begin SetLength(Result, 2); Result[0].CommandID := 'myext.save'; Result[0].ImageIndex := 0; Result[0].Hint := 'Speichern'; Result[0].Enabled := True; Result[0].Visible := True; Result[0].OnExecute := @HandleSave; Result[1].CommandID := 'myext.refresh'; Result[1].ImageIndex := 1; Result[1].Hint := 'Aktualisieren'; Result[1].Enabled := True; Result[1].Visible := True; Result[1].OnExecute := @HandleRefresh; end;
Die QuickBar zeigt zwei Gruppen, getrennt durch eine sichtbare Linie:
| Seite | Quelle | Beispiel |
|---|---|---|
| Links | Editor-Aktionen | Speichern, Rückgängig |
| Rechts | Globale Schnellbefehle | Aktualisieren, Einstellungen |
Wenn mehr Buttons vorhanden sind als Platz in der Titelleiste, erscheint automatisch ein „…“ Überlauf-Menü.
Die WShell stellt einen DataBridge bereit. Damit kann Ihre Extension Datenbanken abfragen oder HTTP-Endpunkte ansprechen — ohne eigenen Low-Level-Code.
Ihre Extension sendet eine Nachricht an die WShell. Die WShell leitet alles, was mit data. beginnt, an den DataBridge weiter:
Ihre Extension --> postMessage({command: 'data.query', ...})
--> SystemHandler
--> DataBridge
--> Datenbank-Provider
--> JSON-Ergebnis zurück an Ihre Extension
| Befehl | Zweck | Gibt zurück |
|---|---|---|
data.query | SELECT-Abfrage | Spalten und Zeilen |
data.execute | INSERT/UPDATE/DELETE | Anzahl betroffener Zeilen |
data.applyDelta | Massenänderungen | Anzahl betroffener Zeilen |
data.getSchema | Tabellenstruktur | Spaltenname, Typ, Nullable |
data.getTables | Tabellen auflisten | Liste der Tabellennamen |
data.ping | Verbindung testen | ok: true/false |
// In Ihrer Extension: // 1. Abfrage an die WShell senden window.wvdxHost.postMessage({ type: 'request', command: 'data.query', id: 1, params: { provider: 'main', query: 'SELECT id, name, email FROM customers WHERE active = :active', parameters: { active: true } } }); // 2. Ergebnis empfangen window.addEventListener('message', function(event) { var msg = event.data; if (msg.id === 1 && msg.result) { console.log('Spalten:', msg.result.columns); console.log('Zeilen:', msg.result.rows); } });
In der settings.json der WShell definieren Sie Ihren Provider:
{
"data.providers": {
"main": {
"type": "sqldb",
"connection": "Host=localhost;Database=mydb;User=app;Password=..."
}
}
}
Wichtig zur Sicherheit:
Die WShell enthält Post-Quantum-Kryptographie (PQC) auf Basis von OpenSSL 3.6. Damit kann Ihre Extension Daten verschlüsseln, bevor sie an einen WebAPI-Endpunkt gesendet werden — zum Beispiel den WvdS Gateway Service.
Herkömmliche Verschlüsselung (RSA, ECDH) wird durch Quantencomputer gebrochen werden. Die neuen NIST-Standards sind gegen Quantenangriffe resistent (mehr dazu):
| Algorithmus | Standard | Zweck |
|---|---|---|
| ML-KEM (Kyber) | FIPS 203 | Schlüsselaustausch |
| ML-DSA (Dilithium) | FIPS 204 | Digitale Signaturen |
| AES-256-GCM | — | Symmetrische Verschlüsselung (schnell, für Nutzdaten) |
| HKDF-SHA256 | — | Schlüsselableitung |
Wenn Ihre Extension crypto.encrypt aufruft, läuft intern ein vierstufiger Prozess:
{ kemCiphertext, aesPayload }Der Empfänger (z.B. Gateway) kehrt den Prozess mit seinem privaten ML-KEM-Schlüssel um.
| Befehl | Zweck |
|---|---|
crypto.isAvailable | Prüfen, ob OpenSSL 3.6 verfügbar ist |
crypto.encrypt | Payload PQ-verschlüsseln (ML-KEM + AES-GCM) |
crypto.decrypt | PQ-verschlüsselten Payload entschlüsseln |
crypto.sign | ML-DSA digitale Signatur erstellen |
crypto.verify | ML-DSA Signatur prüfen |
crypto.generateKeyPair | ML-KEM oder ML-DSA Schlüsselpaar erzeugen |
crypto.keyStore.generate | Schlüsselpaar erzeugen und speichern |
crypto.keyStore.list | Gespeicherte Schlüssel auflisten |
crypto.keyStore.rotate | Schlüssel rotieren (neuer Schlüssel, alter bleibt) |
Der Ablauf in vier Schritten: PQC-Verfügbarkeit prüfen, Schlüsselpaar erzeugen, Payload verschlüsseln, verschlüsseltes Ergebnis per HTTPS an den Gateway senden.
Der WvdS Gateway Service ist ein Backend, das PQ-verschlüsselte Payloads nativ versteht. Er sitzt zwischen Ihrer Extension und dem eigentlichen API:
Extension --> PQ-Encrypt(Payload)
--> HTTPS POST an Gateway
--> Gateway-Middleware entschlüsselt
--> Weiterleitung an Backend-API
--> Antwort wird zurück verschlüsselt
--> Extension entschlüsselt Ergebnis
Der Gateway unterstützt drei Verschlüsselungsmodi:
| Modus | Wann verwendet | Schlüsselquelle |
|---|---|---|
| Session-basiert | X-Session-Id Header vorhanden | ML-KEM-abgeleiteter Sitzungsschlüssel |
| Statisch | Kein Session-Header | HKDF aus MasterSecret |
| Durchleitung | Verschlüsselung deaktiviert | Keine Verschlüsselung |
| Schritt | Thema | Werkzeug / Mechanismus |
|---|---|---|
| 1 | Projekt anlegen | FPC Solution Manager → FPCSE: Start Project Wizard |
| 2 | Formular-Layout entwerfen | pas2js Studio → WFM-Designer (Drag & Drop) |
| 3 | Events binden | Designer → Doppelklick auf Event → Handler wird erzeugt |
| 4 | Daten binden | TContact-Record lokal oder per DataBridge aus DB |
| 5 | Bauen und testen | Solution Manager → Rechtsklick → Extension Build (Build / Install / Package VSIX) |
| 6 | Menü-Integration | contributes.menus mit Ort-Schlüsseln (menuBar/file etc.) |
| 7 | QuickBar-Buttons | IWvdSShellContribution.GetQuickCommands() zur Laufzeit |
| 8 | Datenabfragen | data.query per postMessage an den DataBridge |
| 9 | PQ-Verschlüsselung | crypto.encrypt (ML-KEM + AES-256-GCM) über CryptoBridge + Gateway |