- gcc_amiga.md: 101→606 lines — pipeline, Docker, platform builds, flags, antipatterns, FAQ - trackdisk.md: 178→428 lines — MFM encoding, 16-command reference, antipatterns, FPGA impact - console.md: 244→470 lines — decision guide, TUI/progress cookbooks, antipatterns, pitfalls - layers.md: 224→739 lines — ClipRect engine, LVO API, backfill hooks, 4 antipatterns, optimization - text_fonts.md: 215→708 lines — ColorFont, Compugraphic outlines, 3 cookbooks, 4 antipatterns - windows.md: 370→778 lines — 5 antipatterns, decision guide, 3 cookbooks, modern analogies - menus.md: 378→695 lines — render chain diagram, 5 antipatterns, lifecycle cookbook, 6 FAQ
14 KiB
console.device — Text Terminal I/O
Every CLI/Shell window on the Amiga is powered by console.device. It is a software terminal emulator that sits between raw keyboard input and Intuition window rendering: it translates keycodes into ASCII characters, parses ANSI escape sequences for cursor control and color, and renders text through the window's RastPort. If you need a text interface in an Intuition window — a debugger console, a text editor, a terminal emulator — console.device is the foundation.
Note
For simple file I/O, use dos.library with
CON:windows instead of opening console.device directly. TheCON:andRAW:handlers wrap console.device and provide buffered line editing, window management, and automatic cleanup.
flowchart LR
KB["Keyboard<br/>(raw keycodes)"] --> INPUT["input.device"]
INPUT --> CON["console.device"]
CON -->|"Read: ASCII chars"| APP["Application"]
APP -->|"Write: text + ESC sequences"| CON
CON -->|"Renders text via<br/>RastPort drawing"| WIN["Intuition Window"]
style CON fill:#e8f4fd,stroke:#2196f3,color:#333
style WIN fill:#c8e6c9,stroke:#2e7d32,color:#333
Opening
struct MsgPort *conPort = CreateMsgPort();
struct IOStdReq *con = (struct IOStdReq *)
CreateIORequest(conPort, sizeof(struct IOStdReq));
/* Attach to an Intuition window: */
con->io_Data = (APTR)window;
con->io_Length = sizeof(struct Window);
if (OpenDevice("console.device", CONU_STANDARD, (struct IORequest *)con, 0))
{
/* error — can't open console */
}
Unit Types
| Unit | Constant | Description |
|---|---|---|
| 0 | CONU_STANDARD |
Full-feature console with cursor and scrolling |
| 1 | CONU_CHARMAP |
Character-mapped console (OS 3.0+) — faster for full-screen updates |
| 3 | CONU_SNIPMAP |
Snip-mapped: supports clipboard cut/paste (OS 3.0+) |
| -1 | CONU_LIBRARY |
Library mode — no window, just keymap translation |
Writing Text and Escape Sequences
/* Write text to the console window: */
void ConPuts(struct IOStdReq *con, char *str)
{
con->io_Command = CMD_WRITE;
con->io_Data = (APTR)str;
con->io_Length = -1; /* -1 = null-terminated */
DoIO((struct IORequest *)con);
}
/* Usage: */
ConPuts(con, "Hello, Amiga!\n");
ConPuts(con, "\033[1mBold text\033[0m\n"); /* bold on/off */
ConPuts(con, "\033[33mYellow text\033[0m\n"); /* color */
ConPuts(con, "\033[10;20HText at row 10 col 20"); /* absolute position */
Reading Input
/* Read characters (blocking): */
char buffer[256];
con->io_Command = CMD_READ;
con->io_Data = (APTR)buffer;
con->io_Length = sizeof(buffer);
DoIO((struct IORequest *)con);
/* con->io_Actual = number of bytes read */
/* Non-blocking read via SendIO + WaitPort: */
con->io_Command = CMD_READ;
con->io_Data = (APTR)buffer;
con->io_Length = 1; /* read 1 char at a time */
SendIO((struct IORequest *)con);
/* Wait for input alongside other events: */
ULONG consoleSig = 1 << conPort->mp_SigBit;
ULONG windowSig = 1 << window->UserPort->mp_SigBit;
ULONG sigs = Wait(consoleSig | windowSig);
if (sigs & consoleSig)
{
WaitIO((struct IORequest *)con);
char ch = buffer[0];
/* process character... */
}
ANSI Escape Sequences
Console.device supports a rich subset of ANSI/VT100 escape sequences (CSI = \033[ = ESC + [):
Cursor Movement
| Sequence | Description | Example |
|---|---|---|
\033[nA |
Cursor up n lines | \033[5A = up 5 |
\033[nB |
Cursor down n lines | |
\033[nC |
Cursor right n columns | |
\033[nD |
Cursor left n columns | |
\033[y;xH |
Move to row y, column x (1-based) | \033[1;1H = home |
\033[H |
Home cursor (top-left) | |
\033[6n |
Report cursor position → replies \033[y;xR |
|
\033[s |
Save cursor position | |
\033[u |
Restore cursor position |
Erasing
| Sequence | Description |
|---|---|
\033[J |
Clear from cursor to end of screen |
\033[1J |
Clear from start of screen to cursor |
\033[2J |
Clear entire screen |
\033[K |
Clear from cursor to end of line |
\033[1K |
Clear from start of line to cursor |
\033[2K |
Clear entire line |
Text Attributes (SGR)
| Sequence | Effect |
|---|---|
\033[0m |
Reset all attributes |
\033[1m |
Bold (high intensity) |
\033[3m |
Italic |
\033[4m |
Underline |
\033[7m |
Inverse video (swap fg/bg) |
\033[22m |
Normal intensity (cancel bold) |
\033[23m |
Cancel italic |
\033[24m |
Cancel underline |
Colors
| Sequence | Foreground | Background |
|---|---|---|
\033[30m / \033[40m |
Black | Black |
\033[31m / \033[41m |
Red | Red |
\033[32m / \033[42m |
Green | Green |
\033[33m / \033[43m |
Yellow/Brown | Yellow/Brown |
\033[34m / \033[44m |
Blue | Blue |
\033[35m / \033[45m |
Magenta | Magenta |
\033[36m / \033[46m |
Cyan | Cyan |
\033[37m / \033[47m |
White | White |
\033[39m / \033[49m |
Default | Default |
Note
Color indices map to the Intuition pen palette of the window's screen, not absolute colors. Pen 0 = background, pen 1 = foreground by default.
Amiga-Specific Extensions
| Sequence | Description |
|---|---|
\033[>1h |
Enable auto-scroll |
\033[>1l |
Disable auto-scroll |
\033[ p |
Enable cursor |
\033[0 p |
Disable cursor |
\033[t / \033[b |
Set top/bottom scroll margins |
\033[20h |
Linefeed mode (LF = CR+LF) |
Raw Key Events
In addition to ASCII, console.device reports special keys as multi-byte escape sequences:
| Key | Sequence Received |
|---|---|
| Cursor Up | \033[A |
| Cursor Down | \033[B |
| Cursor Right | \033[C |
| Cursor Left | \033[D |
| Shift+Up | \033[T |
| Shift+Down | \033[S |
| F1–F10 | \033[0~ – \033[9~ |
| Shift+F1–F10 | \033[10~ – \033[19~ |
| Help | \033[?~ |
Proper Shutdown
/* Must abort any pending read before closing: */
if (!CheckIO((struct IORequest *)con))
{
AbortIO((struct IORequest *)con);
WaitIO((struct IORequest *)con);
}
CloseDevice((struct IORequest *)con);
DeleteIORequest((struct IORequest *)con);
DeleteMsgPort(conPort);
CON: and RAW: Handlers
The AmigaDOS file handlers CON: and RAW: are wrappers around console.device:
| Handler | Description |
|---|---|
CON: |
Line-buffered console — input is buffered until Enter is pressed. Supports line editing. |
RAW: |
Raw console — each keypress is delivered immediately. No line editing. |
/* Open a CON: window from DOS: */
BPTR fh = Open("CON:0/0/640/200/My Window/CLOSE", MODE_OLDFILE);
FPuts(fh, "Type something: ");
char buf[80];
FGets(fh, buf, sizeof(buf));
Close(fh);
/* RAW: for unbuffered key-by-key input: */
BPTR raw = Open("RAW:0/0/640/200/Raw Input", MODE_OLDFILE);
/* Each Read returns immediately with 1 char */
References
NDK Headers
devices/conunit.h— unit type constants (CONU_STANDARD, etc.)devices/console.h— console-specific structures
Documentation
- ADCD 2.1: console.device autodocs
- Amiga ROM Kernel Reference Manual: Devices — console chapter
Related Knowledge Base Articles
- keyboard.md — raw keycode to console.device pipeline
- input.md — input handler chain
- cli_shell.md — Shell architecture built on console.device
- windows.md — Intuition window management
Decision Guide: How to Render Text
flowchart TD
START{"Need text output?"} --> WHERE{"Where?"}
WHERE -->|"Shell window"| CON{"Use CON: handler<br>via dos.library"}
WHERE -->|"Own Intuition window"| COMPLEX{"Need cursor, colors,<br>line editing?"}
COMPLEX -->|Yes| CD{"Use console.device"}
COMPLEX -->|No| RP{"Use RastPort text<br>functions directly"}
WHERE -->|"Full-screen TUI"| CD
WHERE -->|"Custom text editor"| CD
| Approach | Use When | Pros | Cons |
|---|---|---|---|
| CON: / RAW: | Simple text I/O in a window | Easiest — just Open() / Write() |
Limited control over rendering |
| console.device | Full-screen TUI, text editor | ANSI escape sequences, cursor, colors | Must manage I/O requests manually |
| RastPort text | Custom text rendering, game UI | Full pixel-level control | No built-in cursor, scrolling, or input handling |
| Intuition gadgets | Forms, menus, buttons | Standard UI elements | Not suitable for free-form text |
Use-Case Cookbook
Full-Screen Text UI (Status Display)
/* con_tui.c — full-screen TUI using console.device */
#include <proto/exec.h>
#include <proto/intuition.h>
#include <proto/dos.h>
void ConPuts(struct IOStdReq *con, const char *str)
{
con->io_Command = CMD_WRITE;
con->io_Data = (APTR)str;
con->io_Length = -1;
DoIO((struct IORequest *)con);
}
void run_tui(struct Window *win, struct IOStdReq *con)
{
/* Clear screen and draw a frame */
ConPuts(con, "\033[2J"); /* clear screen */
ConPuts(con, "\033[1;1H"); /* home cursor */
ConPuts(con, "\033[7m Status Monitor \033[0m\n"); /* inverse title */
ConPuts(con, "\033[3;1H\033[33mCPU: \033[32m7.14 MHz\033[0m");
ConPuts(con, "\033[4;1H\033[33mChip: \033[32m512 KB\033[0m");
ConPuts(con, "\033[20;1H\033[7m Press ESC to exit \033[0m");
/* Main loop — wait for keypress */
char buf[8];
con->io_Command = CMD_READ;
con->io_Data = (APTR)buf;
con->io_Length = sizeof(buf);
DoIO((struct IORequest *)con);
/* Check if ESC was pressed (0x1B) */
}
Progress Bar Using Escape Sequences
void DrawProgressBar(struct IOStdReq *con, int row, int percent)
{
char buf[80];
int bar_width = 40;
int filled = (percent * bar_width) / 100;
/* Move to row, column 10 */
RawDoFmt("\033[%ld;10H", (RAWARG)&row, NULL, buf);
ConPuts(con, buf);
/* Draw filled portion in green, empty in black */
ConPuts(con, "\033[42m"); /* green background */
for (int i = 0; i < filled; i++) ConPuts(con, " ");
ConPuts(con, "\033[40m"); /* black background */
for (int i = filled; i < bar_width; i++) ConPuts(con, " ");
ConPuts(con, "\033[0m"); /* reset */
/* Percentage text */
RawDoFmt(" %ld%%", (RAWARG)&percent, NULL, buf);
ConPuts(con, buf);
}
Best Practices
- Use
CON:via dos.library for simple text I/O — avoid opening console.device directly unless you need escape sequence control - Always save/restore cursor position around multi-step output operations
- Reset all text attributes (
\033[0m) at the end of every output operation — stale attributes cause rendering bugs - Use
CONU_SNIPMAP(OS 3.0+) for clipboard support in text editors - Set scroll margins with
\033[t/\033[bfor status bars that stay fixed - Always abort pending reads before closing the device — see Proper Shutdown above
- Handle both ASCII and escape-sequence input when reading — cursor keys arrive as
\033[Aetc.
Named Antipatterns
"The Leaking Read" — Pending I/O on Shutdown
/* BAD: Pending async read never completed */
con->io_Command = CMD_READ;
con->io_Data = (APTR)buffer;
con->io_Length = 256;
SendIO((struct IORequest *)con);
/* ... user closes window ... */
CloseDevice((struct IORequest *)con); /* CRASH: pending I/O */
/* CORRECT: Abort pending I/O before closing */
if (!CheckIO((struct IORequest *)con))
{
AbortIO((struct IORequest *)con);
WaitIO((struct IORequest *)con);
}
CloseDevice((struct IORequest *)con);
"The Attribute Leak" — Stale Colors After Output
/* BAD: Set color but never reset — next output inherits it */
ConPuts(con, "\033[31mError!");
/* Next write is still red! */
ConPuts(con, "Normal text"); /* Still red! */
/* CORRECT: Always reset attributes */
ConPuts(con, "\033[31mError!\033[0m");
ConPuts(con, "Normal text"); /* Correctly default color */
"The Blocking Shell" — DoIO Read Freezes UI
/* BAD: DoIO on CMD_READ blocks forever if no input comes */
con->io_Command = CMD_READ;
con->io_Length = 1;
DoIO((struct IORequest *)con); /* Frozen — can't handle IDCMP events */
/* CORRECT: Use SendIO + Wait with IDCMP signals */
con->io_Command = CMD_READ;
con->io_Length = 1;
SendIO((struct IORequest *)con);
ULONG conSig = 1 << conPort->mp_SigBit;
ULONG winSig = 1 << window->UserPort->mp_SigBit;
ULONG sigs = Wait(conSig | winSig);
if (sigs & winSig) {
/* Handle IDCMP event (close window, etc.) */
AbortIO((struct IORequest *)con);
WaitIO((struct IORequest *)con);
}
if (sigs & conSig) {
WaitIO((struct IORequest *)con);
/* Process input character */
}
Pitfalls & Common Mistakes
1. Console Position is 1-Based, Not 0-Based
Symptom: Text appears one row/column off from expected position.
Cause: ANSI H command uses 1-based coordinates: \033[1;1H is the top-left corner, not \033[0;0H.
Fix: Always add 1 to your 0-based coordinates.
2. Writing to a Closed Window
Symptom: Guru meditation or silent crash.
Cause: Console.device renders through the window's RastPort. If the window is closed (user clicked close gadget), writing to the console device dereferences a stale window pointer.
Fix: Monitor IDCMP_CLOSEWINDOW and abort all console I/O before closing.
3. Buffer Overrun on Read
Symptom: Memory corruption.
Cause: CMD_READ returns up to io_Length bytes. If you allocated a smaller buffer, the device writes past the end.
Fix: Always ensure io_Length <= sizeof(buffer).
FAQ
Q: What is the difference between CON: and RAW:?
A: CON: is line-buffered — input is collected until the user presses Enter, with line editing (backspace, cursor keys). RAW: delivers each keypress immediately with no editing. Use RAW: for games and interactive TUIs; use CON: for command-line tools.
Q: Can I use console.device without an Intuition window?
A: Yes — open with CONU_LIBRARY unit type. This gives access to the keymap translation without rendering. Useful for translating raw keycodes to ASCII.
Q: How do I create a console with a specific font size?
A: Open the window with the desired font, then open console.device on that window. The console uses the window's RastPort font. Set the font with SetFont(window->RPort, myFont) before opening the console.
Q: Why does my text look wrong after a screen drag?
A: Console.device renders into the window's RastPort. When the screen is dragged, the console does not automatically redraw. You must handle IDCMP_NEWSIZE and redraw the console content.