diff --git a/src/cli/qr-terminal.ts b/src/cli/qr-terminal.ts index bdf3cf4..7237581 100644 --- a/src/cli/qr-terminal.ts +++ b/src/cli/qr-terminal.ts @@ -19,10 +19,40 @@ import { generateQRMatrix } from '../core/qr/qr_encode'; import { packetize } from '../core/sender/packetizer'; import { scheduleFrames } from '../core/sender/scheduler'; import { QR_VERSION, ECC_LEVEL } from '../core/protocol/constants'; -import { clearScreen, hideCursor, showCursor, renderToTerminal, moveCursorUp } from './terminal_raster'; +import { + enterAltBuffer, + exitAltBuffer, + clearScreen, + hideCursor, + showCursor, + renderToTerminal, + moveCursorUp, +} from './terminal_raster'; const FPS_MS = 100; +// ───────────────────────────────────────────────────────────────────────────────── +// Help text +// ───────────────────────────────────────────────────────────────────────────────── + +const HELP_TEXT = ` +QR Terminal Display – encode text or a file into a looping QR-code sequence. + +Usage: + qr-terminal [file] read from file + echo "text" | qr-terminal read from stdin + +Controls: + q, Q quit + Ctrl-C quit + +The app uses the same V10-M QR protocol as the web transfer demo. +`; + +function showHelp(): void { + console.log(HELP_TEXT.trim()); +} + // ───────────────────────────────────────────────────────────────────────────────── // Argument parsing // ───────────────────────────────────────────────────────────────────────────────── @@ -62,6 +92,12 @@ function buildFrames(data: Uint8Array): Uint8Array[] { // ───────────────────────────────────────────────────────────────────────────────── function main() { + const args = process.argv.slice(2); + if (args.includes('-h') || args.includes('--help')) { + showHelp(); + process.exit(0); + } + let data: Uint8Array; try { data = readInput(); @@ -114,7 +150,6 @@ function main() { } if (firstDraw) { - clearScreen(); firstDraw = false; } else { // Move cursor back to the first QR line so we overwrite in-place @@ -128,15 +163,19 @@ function main() { function cleanup() { running = false; clearInterval(interval); - clearScreen(); + exitAltBuffer(); showCursor(); if (ttyFd !== null) { try { closeSync(ttyFd); } catch {} } - process.stdout.write('QR terminal display stopped.\n'); + console.log('QR terminal display stopped.'); process.exit(0); } + // Switch to alternate buffer and clear it before drawing + enterAltBuffer(); + clearScreen(); + // Keyboard handling — try /dev/tty first so it works even when stdin is a pipe let ttyFd: number | null = null; try { diff --git a/src/cli/terminal_raster.ts b/src/cli/terminal_raster.ts index bad2fa6..a6f7214 100644 --- a/src/cli/terminal_raster.ts +++ b/src/cli/terminal_raster.ts @@ -65,6 +65,20 @@ export function renderToTerminal(matrix: boolean[][], quietZone: number = 4): st return lines; } +/** + * Enter the alternate screen buffer (preserves normal buffer on exit). + */ +export function enterAltBuffer(): void { + process.stdout.write('\x1b[?1049h'); +} + +/** + * Exit the alternate screen buffer and restore the normal buffer. + */ +export function exitAltBuffer(): void { + process.stdout.write('\x1b[?1049l'); +} + /** * Clear the terminal screen and move cursor to home position. */ diff --git a/src/tests/cli_rendering.test.ts b/src/tests/cli_rendering.test.ts index 9ee63e0..cb054de 100644 --- a/src/tests/cli_rendering.test.ts +++ b/src/tests/cli_rendering.test.ts @@ -148,15 +148,41 @@ describe('Terminal Rasterizer', () => { }); describe('CLI Screen Helpers', () => { - it('should produce escape sequences', async () => { + it('should expose clearScreen', async () => { const { clearScreen } = await import('@/cli/terminal_raster'); expect(typeof clearScreen).toBe('function'); }); - it('should produce cursor-up sequence', async () => { + it('should expose moveCursorUp', async () => { const { moveCursorUp } = await import('@/cli/terminal_raster'); expect(typeof moveCursorUp).toBe('function'); }); + + it('should expose alt-buffer helpers', async () => { + const { enterAltBuffer, exitAltBuffer } = await import('@/cli/terminal_raster'); + expect(typeof enterAltBuffer).toBe('function'); + expect(typeof exitAltBuffer).toBe('function'); + }); +}); + +describe('CLI Help Flag', () => { + it('should not throw when help text is constructed', () => { + const helpText = ` +QR Terminal Display – encode text or a file into a looping QR-code sequence. + +Usage: + qr-terminal [file] read from file + echo "text" | qr-terminal read from stdin + +Controls: + q, Q quit + Ctrl-C quit + +The app uses the same V10-M QR protocol as the web transfer demo. +`; + expect(helpText).toContain('Usage:'); + expect(helpText).toContain('quit'); + }); }); describe('CLI Encoder Pipeline', () => {