feat(editor): F4.6b the command line on a computer's desktop

A new oracle, TerminalWindowRun, types into Java's own command line
window: the window writes each line after its prompt, takes a blank
line, cls, reset and exit itself, keeps the lines for the arrow keys,
and keeps its text, history and working directory over a stop and a
start.

The room now holds a command line window per computer for everyone
(TerminalWindow), kept over stops; the protocol carries its lines and
the one way to read them (terminalScreen); the core goes on in the
working directory of the run before (continueIn). The desktop opens
programs in Java's frame, and the command line works with keyboard and
finger: grey on black, the prompt hidden while a command runs, history,
chips for the commands, Ctrl+C. The room's failure is now tested in the
browser.

Fixed: the room handed the engine the wall clock with fractions of a
millisecond, and a ping hung after its first timeout. The engine and
the room count whole milliseconds, as Java's clock does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-03 14:22:49 +00:00
co-authored by Claude Opus 5.5
parent dfd5ec03ce
commit ac48e969f4
23 changed files with 1255 additions and 47 deletions
+26
View File
@@ -1239,3 +1239,29 @@ Gemessen (03.10.2026):
Karte; ohne Karte behält der Router kein Gateway (`setStandardGateway`
schreibt es in die Karten, `getStandardGateway` liest `null`).
### Das Fenster der Befehlszeile (F4.6b)
`TerminalWindowRun` öffnet Javas `GUIApplicationTerminalWindow` auf einem
`GUIDesktopPanel` eines Rechners ohne Netz, gibt Tasten direkt an den
Tastenhörer des Eingabefelds (Enter, Pfeil hoch/runter), stoppt und startet
das System einmal (`beenden`, `starten`, wie ein Moduswechsel) und schreibt
nach jeder Taste Text, Prompt, ob er sichtbar ist, und die Eingabezeile
(`packages/sim/test/terminalWindow.java.json`).
Gemessen (03.10.2026):
* Eine leere Zeile, auch nur aus Leerzeichen (`StringUtils.isBlank`), gibt
„␣␣\n“; jede andere Zeile steht danach als „\n“ + Prompt + Zeile + „\n“
im Fenster, auch `cls`, `reset` und `exit`, bevor das Fenster sie selbst
erledigt.
* `cls`/`reset` zeigen wieder den Startbildschirm; `exit` schließt das
Fenster, sein Text bleibt.
* Pfeil hoch geht durch alle eingegebenen Zeilen zurück (leere nicht),
runter vor, unter der neuesten steht eine leere Zeile; über die Enden
hinaus bleibt es stehen.
* Der Prompt folgt dem Arbeitsverzeichnis, nachgeführt, wenn ein Befehl
fertig ist (`cd dir` → „/dir> “).
* Nach Stopp und Start bleiben Text, Verlauf **und** Arbeitsverzeichnis:
Javas Terminal setzt den Ordner nur bei der Installation
(`setSystemSoftware`), und das Fenster ist dasselbe Objekt.
+9 -1
View File
@@ -21,7 +21,7 @@ if [ ! -f "$upstream/target/filius.jar" ]; then
fi
classpath="$upstream/target/filius.jar:$upstream/target/lib/*"
mkdir -p "$here/.build"
javac -encoding UTF-8 -d "$here/.build" -cp "$classpath" "$here/src/FlsOracle.java" "$here/src/GetterDefaults.java" "$here/src/IpCheckCases.java" "$here/src/DnsHostsCases.java" "$here/src/EmailFileCases.java" "$here/src/BeansDump.java" "$here/src/Base64Cases.java" "$here/src/ScenarioNetwork.java" "$here/src/DhcpRun.java" "$here/src/TcpRun.java" "$here/src/DnsRun.java" "$here/src/ArpRun.java" "$here/src/RipRun.java" "$here/src/NatRun.java" "$here/src/WlanRun.java" "$here/src/ExampleRun.java" "$here/src/EchoRun.java" "$here/src/WebRun.java" "$here/src/RouterWebRun.java" "$here/src/KonfigCases.java" "$here/src/EmailRun.java" "$here/src/MailCases.java" "$here/src/EmailClientRun.java" "$here/src/TerminalCases.java" "$here/src/TerminalRun.java" "$here/src/FileCases.java" "$here/src/GnutellaRun.java" "$here/src/NewDeviceRun.java" "$here/src/SheetRun.java"
javac -encoding UTF-8 -d "$here/.build" -cp "$classpath" "$here/src/FlsOracle.java" "$here/src/GetterDefaults.java" "$here/src/IpCheckCases.java" "$here/src/DnsHostsCases.java" "$here/src/EmailFileCases.java" "$here/src/BeansDump.java" "$here/src/Base64Cases.java" "$here/src/ScenarioNetwork.java" "$here/src/DhcpRun.java" "$here/src/TcpRun.java" "$here/src/DnsRun.java" "$here/src/ArpRun.java" "$here/src/RipRun.java" "$here/src/NatRun.java" "$here/src/WlanRun.java" "$here/src/ExampleRun.java" "$here/src/EchoRun.java" "$here/src/WebRun.java" "$here/src/RouterWebRun.java" "$here/src/KonfigCases.java" "$here/src/EmailRun.java" "$here/src/MailCases.java" "$here/src/EmailClientRun.java" "$here/src/TerminalCases.java" "$here/src/TerminalRun.java" "$here/src/FileCases.java" "$here/src/GnutellaRun.java" "$here/src/NewDeviceRun.java" "$here/src/SheetRun.java" "$here/src/TerminalWindowRun.java"
# FILIUS writes its settings below $HOME; keep them out of the real one.
mkdir -p "$here/.build/home"
# Removed first: a crashed run must not leave the previous result standing.
@@ -337,6 +337,14 @@ HOME="$here/.build/home" xvfb-run -a java -cp "$classpath:$here/.build" SheetRun
"$root/packages/doc/test/sheet.cases.txt" "$output" 2>&1 | grep -v 'logback\|^$' || true
test -s "$output"
# FILIUS' command line window as it shows what is typed into it, key by key,
# over a stop and a start (TerminalWindowRun): the room's window
# (packages/sim/test/terminalWindow.test.ts).
output="$root/packages/sim/test/terminalWindow.java.json"
rm -f "$output"
HOME="$here/.build/home" xvfb-run -a java -Duser.language=de -Duser.country=DE -cp "$classpath:$here/.build" TerminalWindowRun "$output" 2>&1 | grep -v 'logback\|^$' || true
test -s "$output"
# FILIUS' own examples as Java FILIUS runs them, opened and started as FILIUS
# does (ExampleRun): the acceptance of F2 (packages/fls/test/examplesJava.test.ts)
# and the classics of F3 (web, email, gnutella; packages/fls/test/classicsJava.test.ts).
@@ -0,0 +1,108 @@
// Types into FILIUS' own command line window (GUIApplicationTerminalWindow on
// a GUIDesktopPanel) of a computer without a network, key by key as its key
// listener hears them, and writes after each key what the window shows: its
// text, its prompt and whether the prompt is shown, the input line. Then the
// simulation is stopped and started again (beenden, starten), as a change of
// mode does, and the window and the working directory are read again: the
// reference for the editor's command line window (packages/sim/test/
// terminalWindow.test.ts).
//
// Usage: java -cp filius.jar:lib/*:classes TerminalWindowRun <output json>
import filius.gui.anwendungssicht.GUIApplicationTerminalWindow;
import filius.gui.anwendungssicht.GUIDesktopPanel;
import filius.hardware.knoten.Rechner;
import filius.software.system.Betriebssystem;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.io.FileOutputStream;
import java.lang.reflect.Field;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.swing.JLabel;
import javax.swing.JTextArea;
import javax.swing.JTextField;
public class TerminalWindowRun {
static final String TERMINAL = "filius.software.lokal.Terminal";
static Object field(Object owner, String name) throws Exception {
Field field = GUIApplicationTerminalWindow.class.getDeclaredField(name);
field.setAccessible(true);
return field.get(owner);
}
static Map<String, Object> shown(GUIApplicationTerminalWindow window, String step) throws Exception {
Map<String, Object> result = new LinkedHashMap<>();
result.put("step", step);
result.put("text", ((JTextArea) field(window, "terminalField")).getText());
JLabel label = (JLabel) field(window, "inputLabel");
result.put("prompt", label.getText());
result.put("promptShown", label.isVisible());
result.put("input", ((JTextField) field(window, "inputField")).getText());
return result;
}
static void press(JTextField input, int key, int modifiers) {
KeyEvent event = new KeyEvent(input, KeyEvent.KEY_PRESSED, System.currentTimeMillis(), modifiers, key, KeyEvent.CHAR_UNDEFINED);
for (KeyListener listener : input.getKeyListeners()) listener.keyPressed(event);
}
/** Waits until the program has answered: the prompt is shown again (or a second passed). */
static void settle(GUIApplicationTerminalWindow window) throws Exception {
JLabel label = (JLabel) field(window, "inputLabel");
for (int waited = 0; waited < 2000 && !label.isVisible(); waited += 20) Thread.sleep(20);
Thread.sleep(200);
}
public static void main(String[] args) throws Exception {
try {
run(args);
} catch (Throwable error) {
error.printStackTrace();
System.exit(1);
}
System.exit(0);
}
static void run(String[] args) throws Exception {
Rechner host = new Rechner();
Betriebssystem system = (Betriebssystem) host.getSystemSoftware();
system.installApp(TERMINAL, true);
system.starten();
GUIDesktopPanel desktop = new GUIDesktopPanel(system);
GUIApplicationTerminalWindow window = (GUIApplicationTerminalWindow) desktop.starteAnwendung(TERMINAL);
JTextField input = (JTextField) field(window, "inputField");
List<Object> steps = new ArrayList<>();
steps.add(shown(window, "open"));
// A line typed and entered ("type:<line>"), a key ("up", "down", "ctrl+c"), a stop and start ("restart").
String[] keys = { "enter:", "enter: ", "enter:pwd", "enter:mkdir dir", "enter:cd dir", "enter:pwd", "enter:nosuchcommand", "up", "up", "up", "down", "down", "down",
"enter:cls", "enter:ls", "restart", "enter:pwd", "up", "enter:exit" };
for (String key : keys) {
if (key.startsWith("enter:")) {
input.setText(key.substring("enter:".length()));
press(input, KeyEvent.VK_ENTER, 0);
settle(window);
} else if (key.equals("up")) {
press(input, KeyEvent.VK_UP, 0);
} else if (key.equals("down")) {
press(input, KeyEvent.VK_DOWN, 0);
} else if (key.equals("restart")) {
system.beenden();
system.starten();
Thread.sleep(300);
}
steps.add(shown(window, key));
}
Map<String, Object> output = new LinkedHashMap<>();
output.put("steps", steps);
try (FileOutputStream out = new FileOutputStream(args[0])) {
out.write((FlsOracle.Json.write(output, "") + "\n").getBytes(StandardCharsets.UTF_8));
}
System.out.println("terminal window steps: " + steps.size());
}
}