(~GuiComponent~, ~TextCanvas~, ~TextEditComponent~) that games can
also use (in-game consoles, interactive screens).
+ *Sixth* is the concrete product built on that library: the virtual
- workspace you actually work in. All application-integration bridges
- (PTY/terminal, future VNC client, window capture) live here — the
- engine must never gain native dependencies or application-domain
- concepts.
+ workspace you actually work in. Application-integration bridges
+ (PTY/terminal, future VNC client, window capture) live here.
+ Exception to the "no native code in the engine" rule: XR glasses
+ head tracking (RayNeo IMU over hidraw, ~eu.svjatoslav.sixth.e3d.gui.headtrack~)
+ lives in the ENGINE, so every app and demo gets look-around head
+ tracking automatically when glasses are plugged in — including
+ hot-plug at runtime.
+ *Sixth 3D Demos* remains the capability gallery and regression
harness. Workspace features are prototyped as demos, but the real
thing lives in Sixth.
if (args.length > 0 && "--selftest".equals(args[0]))
System.exit(runSelfTest(workspace));
+ if (args.length > 0 && "--headtest".equals(args[0]))
+ System.exit(runHeadTest(workspace));
+
+ if (args.length > 0 && "--headdebug".equals(args[0]))
+ startHeadDebugLogger(workspace);
+
Runtime.getRuntime().addShutdownHook(
new Thread(workspace::close));
}
return 0;
}
+ /**
+ * Prints head tracker diagnostics every 2 seconds (frame flow, tick,
+ * dt, max gyro excursion, fused angles) so a live look-around test
+ * can be verified from the process log afterwards.
+ */
+ private static void startHeadDebugLogger(final Workspace workspace) {
+ final Thread logger = new Thread(() -> {
+ while (true) {
+ final var tracker = workspace.getHeadTracker();
+ System.out.println("HEADTRACK "
+ + (tracker == null ? "no device"
+ : tracker.getDebugString()));
+ try {
+ Thread.sleep(2000);
+ } catch (final InterruptedException e) {
+ return;
+ }
+ }
+ }, "head-debug-logger");
+ logger.setDaemon(true);
+ logger.start();
+ }
+
+ /**
+ * Prints head tracker and camera angles for 30 seconds so axis/sign
+ * conventions can be verified against real head movement. Exit 0 if
+ * the tracker produced data at all.
+ */
+ private static int runHeadTest(final Workspace workspace)
+ throws InterruptedException {
+ final var tracker = workspace.getHeadTracker();
+ if (tracker == null) {
+ System.out.println("HEADTEST FAIL: no glasses detected");
+ return 1;
+ }
+ // frame listeners do not run under xvfb (no render loop), so do
+ // the boot recenter here and compute what HeadLookController
+ // would apply, in-line
+ while (!tracker.isCalibrated())
+ Thread.sleep(50);
+ Thread.sleep(1000); // let the filter settle
+ tracker.recenter();
+ final var camera = workspace.getViewFrame().getViewPanel()
+ .getCamera();
+ final double[] baseAngles = camera.getTransform().getRotation()
+ .toAngles();
+ System.out.println("wait for calibration, then: turn head LEFT, "
+ + "watch yaw; nod DOWN, watch pitch (30s)");
+ final long deadline = System.currentTimeMillis() + 30_000;
+ boolean sawMovement = false;
+ while (System.currentTimeMillis() < deadline) {
+ final double headYaw = tracker.getLookYaw();
+ final double headPitch = tracker.getLookPitch();
+ // frame listeners do not run under xvfb (no render loop), so
+ // compute what HeadLookController would apply, in-line
+ final double camYaw = baseAngles[0] - headYaw;
+ final double camPitch = baseAngles[1] + headPitch;
+ if (Math.abs(headYaw) > 0.05 || Math.abs(headPitch) > 0.05)
+ sawMovement = true;
+ System.out.printf("head yaw %+7.1f pitch %+7.1f deg | "
+ + "camera yaw %+7.3f pitch %+7.3f rad%n",
+ Math.toDegrees(headYaw), Math.toDegrees(headPitch),
+ camYaw, camPitch);
+ Thread.sleep(200);
+ }
+ workspace.close();
+ System.out.println(sawMovement ? "HEADTEST PASS (movement seen)"
+ : "HEADTEST FAIL (no head movement detected)");
+ return sawMovement ? 0 : 1;
+ }
+
private static boolean waitFor(final TerminalPanel terminal,
final String needle)
throws InterruptedException {
* {@link PtySession}) rendered onto a {@link TextCanvas}.
*
* <p>Click the panel to focus it; keystrokes are translated to terminal
- * input bytes and written to the PTY. ESC releases focus (engine focus
- * model, same as the text editor).</p>
+ * input bytes and written to the PTY. Shift+ESC releases focus (plain
+ * ESC goes to the shell — terminal programs need it).</p>
*
* <p>Terminal output arrives on the PTY reader thread, which copies the
* screen buffer into the canvas and requests a repaint. This is the same
@Override
public boolean keyPressed(final KeyEvent event,
final ViewPanel viewPanel) {
- // Focus model: Ctrl+ESC releases focus (GuiComponent convention
+ // Focus model: Shift+ESC releases focus (GuiComponent convention
// uses plain ESC, but terminal programs need ESC themselves —
- // mc uses ESC for dialogs and ESC 0 for F10).
+ // mc uses ESC for dialogs and ESC 0 for F10. Terminal apps cannot
+ // distinguish Shift+ESC from plain ESC on the wire, so no program
+ // loses a binding; Ctrl+ESC was avoided because desktops commonly
+ // intercept it).
if (event.getKeyChar() == '\u001B') {
- if (event.isControlDown())
+ if (event.isShiftDown())
return super.keyPressed(event, viewPanel);
session.send("\u001B");
return true;