Expand documentation suite: 30+ articles enriched with diagrams, code examples, and hardware details

Graphics: text_fonts (bitmap layout, styles), sprites (DMA, multiplexing), gfx_base (chipset detection), rastport (draw modes, clipping), ham_ehb (mermaid fixes), display_modes (HAM palettes)

Devices: scsi (per-model interfaces, Gayle limits, CD-ROM, native vs vendor drivers), console (ANSI sequences, CON:/RAW:), parallel (CIA registers, pinout), timer (resource exhaustion), gameport (quadrature, XOR state)

Libraries: workbench (WBStartup, AppWindow/Icon/MenuItem), rexxsyslib (ARexx port hosting, command parsing), diskfont (font directory, colour fonts), keymap (rawkey codes, dead keys), locale (catalogue system, date formatting), layers (ClipRect, refresh types), utility (TagItem chains), icon (DiskObject, ToolTypes), iffparse (IFF structure, ByteRun1), expansion (Zorro AutoConfig)

Networking: tcp_ip_stacks (major rewrite - Amiga vs Unix architecture, SANA-II pipeline, PPP/SLIP dial-up, Ethernet cards, MiSTer), bsdsocket (pure API ref), sana2 (buffer hooks, driver requirements), protocols (all code examples). Deduplicated overlap between the three files.

Toolchain: debugging (Enforcer patterns, SnoopDOS, GDB remote, kprintf checklist), sasc (pragma encoding, __saveds idioms), stormc (NEW - StormC IDE, C++, PowerPC)

References: error_codes (DOS, Exec, trackdisk, Intuition error tables)
Driver development: rtg_driver (Native driver analysis, P96 tuning)

All 22 README indexes updated. Root README synced with stormc.md entry.
This commit is contained in:
Ilia Sharin 2026-04-23 21:37:26 -04:00
parent 0ded078134
commit f61c26b542
38 changed files with 6402 additions and 1065 deletions

View file

@ -2,18 +2,20 @@
# Common Libraries — Overview
Shared libraries beyond the core exec/dos/graphics/intuition subsystems. These provide utility functions, file format parsing, hardware expansion support, and Workbench integration.
## Section Index
| File | Description |
|---|---|
| [utility.md](utility.md) | utility.library — TagItem, hooks, date |
| [expansion.md](expansion.md) | expansion.library — Zorro/AutoConfig |
| [icon.md](icon.md) | icon.library — Workbench icons (.info) |
| [workbench.md](workbench.md) | workbench.library — Workbench integration |
| [iffparse.md](iffparse.md) | iffparse.library — IFF file parsing |
| [locale.md](locale.md) | locale.library — internationalisation |
| [keymap.md](keymap.md) | keymap.library — keyboard mapping |
| [rexxsyslib.md](rexxsyslib.md) | rexxsyslib.library — ARexx interface |
| [mathffp.md](mathffp.md) | mathffp/mathieeesingbas — floating point |
| [layers.md](layers.md) | layers.library — window clipping layers |
| [diskfont.md](diskfont.md) | diskfont.library — disk-based fonts |
| [utility.md](utility.md) | TagItem lists with chaining (TAG_MORE), callback hooks (register convention), date/time utilities, tag iteration patterns |
| [expansion.md](expansion.md) | Zorro II/III bus architecture, AutoConfig ROM layout, board enumeration, FPGA implementation notes |
| [icon.md](icon.md) | Workbench icons (.info): DiskObject structure, ToolType parsing, icon types, OS 3.5+ true-colour icons |
| [workbench.md](workbench.md) | Workbench integration: WBStartup handling, AppWindow drag-and-drop, AppIcon, AppMenuItem |
| [iffparse.md](iffparse.md) | IFF file parsing: ILBM/8SVX/ANIM, BitMapHeader, ByteRun1 compression, clipboard integration |
| [locale.md](locale.md) | Internationalisation: catalogue system (.cd/.ct files), locale-aware date/number formatting, character classification |
| [keymap.md](keymap.md) | Keyboard mapping: raw-to-ASCII translation, KeyMap structure, dead keys, rawkey codes, national layouts |
| [rexxsyslib.md](rexxsyslib.md) | ARexx scripting: hosting ARexx ports, command parsing, sending commands, return codes |
| [mathffp.md](mathffp.md) | Motorola FFP and IEEE 754 floating point |
| [layers.md](layers.md) | Window clipping: ClipRect engine, Simple/Smart/Super refresh, damage repair, backfill hooks, layer locking |
| [diskfont.md](diskfont.md) | Disk-based fonts: FONTS: directory structure, AvailFonts enumeration, colour fonts (OS 3.0+) |

View file

@ -1,10 +1,48 @@
[← Home](../README.md) · [Libraries](README.md)
# diskfont.library — Disk-Based Fonts
# diskfont.library — Disk-Based Font Loading
## Overview
`diskfont.library` loads bitmap fonts from disk (the `FONTS:` assign). ROM fonts (topaz 8, topaz 9) are always available; all others require this library.
`diskfont.library` loads bitmap fonts from disk (the `FONTS:` assign). Only two fonts are built into ROM — **topaz 8** and **topaz 9**. All other fonts (helvetica, times, courier, etc.) must be loaded from disk via this library.
```mermaid
flowchart LR
APP["Application"] -->|"OpenDiskFont(&ta)"| DFL["diskfont.library"]
DFL -->|"Scans FONTS: directory"| FONTS["FONTS:<br/>helvetica.font<br/>helvetica/24"]
DFL -->|"Returns loaded font"| TF["struct TextFont"]
TF -->|"SetFont(rp, font)"| RP["RastPort"]
ROM["ROM"] -->|"OpenFont(&ta)"| TOPAZ["topaz 8/9<br/>(always available)"]
style DFL fill:#e8f4fd,stroke:#2196f3,color:#333
style FONTS fill:#c8e6c9,stroke:#2e7d32,color:#333
```
---
## Font Directory Structure
Amiga bitmap fonts are stored as a descriptor file plus per-size data files:
```
FONTS:
helvetica.font ← font descriptor (FontContents header)
helvetica/
9 ← bitmap data for 9-pixel height
11 ← bitmap data for 11-pixel height
13 ← bitmap data for 13-pixel height
18 ← bitmap data for 18-pixel height
24 ← bitmap data for 24-pixel height
times.font
times/
11
13
18
24
```
The `.font` descriptor file contains a `FontContentsHeader` listing all available sizes, styles, and flags.
---
@ -13,45 +51,106 @@
```c
struct Library *DiskfontBase = OpenLibrary("diskfont.library", 0);
/* Request a specific font and size: */
struct TextAttr ta = {"helvetica.font", 24, 0, 0};
struct TextFont *font = OpenDiskFont(&ta);
if (font) {
if (font)
{
SetFont(rp, font);
/* ... render ... */
Move(rp, 10, 30);
Text(rp, "Disk Font Text", 14);
/* When done with the font: */
CloseFont(font);
}
else
{
/* Font not found — fall back to ROM font: */
struct TextAttr fallback = {"topaz.font", 8, 0, FPF_ROMFONT};
struct TextFont *topaz = OpenFont(&fallback);
SetFont(rp, topaz);
}
CloseLibrary(DiskfontBase);
```
---
### Size Matching
## Font Directory Structure
```
FONTS:
helvetica.font ← font descriptor file
helvetica/
24 ← bitmap data for size 24
11 ← bitmap data for size 11
```
If the exact requested size is not available, `OpenDiskFont` returns NULL. To find the nearest available size, use `AvailFonts` to enumerate, then request the closest match.
---
## Listing Available Fonts
## Enumerating Available Fonts
```c
struct List *fontList = NULL;
LONG count = AvailFonts(buf, bufsize, AFF_DISK | AFF_MEMORY);
/* AvailFonts returns all fonts in ROM and on disk: */
LONG bufSize = 4096;
APTR buf = AllocMem(bufSize, MEMF_ANY | MEMF_CLEAR);
LONG shortfall = AvailFonts(buf, bufSize, AFF_DISK | AFF_MEMORY);
if (shortfall > 0)
{
/* Buffer too small — reallocate and retry: */
FreeMem(buf, bufSize);
bufSize += shortfall;
buf = AllocMem(bufSize, MEMF_ANY | MEMF_CLEAR);
AvailFonts(buf, bufSize, AFF_DISK | AFF_MEMORY);
}
struct AvailFontsHeader *afh = (struct AvailFontsHeader *)buf;
struct AvailFonts *af = (struct AvailFonts *)&afh[1];
for (int i = 0; i < afh->afh_NumEntries; i++) {
Printf("%s %ld\n", af[i].af_Attr.ta_Name, af[i].af_Attr.ta_YSize);
for (int i = 0; i < afh->afh_NumEntries; i++)
{
Printf("%s size %-3ld %s\n",
af[i].af_Attr.ta_Name,
af[i].af_Attr.ta_YSize,
(af[i].af_Type & AFF_DISK) ? "disk" :
(af[i].af_Type & AFF_MEMORY) ? "ROM" : "scaled");
}
FreeMem(buf, bufSize);
```
---
## Font Types
| Flag | Source | Notes |
|---|---|---|
| `AFF_MEMORY` | ROM or already loaded in memory | topaz, or previously opened disk fonts |
| `AFF_DISK` | Available on `FONTS:` | Requires disk access to load |
| `AFF_SCALED` | Algorithmically scaled from another size | Lower quality; avoid when native size exists |
| `AFF_BITMAP` | Bitmap (pixel) font | Standard Amiga font format |
| `AFF_TAGGED` | Tagged (OS 3.0+ extended) font | Supports colour fonts, outlined fonts |
---
## Colour Fonts (OS 3.0+)
OS 3.0 introduced **colour bitmap fonts** — each glyph can have multiple bitplanes:
```c
/* Colour fonts use ColorTextFont — an extension of TextFont: */
struct ColorTextFont {
struct TextFont ctf_TF; /* standard TextFont */
UWORD ctf_Flags; /* CT_COLORFONT etc. */
UBYTE ctf_Depth; /* number of bitplanes */
UBYTE ctf_FgColor; /* default foreground pen */
UBYTE ctf_Low; /* lowest colour used */
UBYTE ctf_High; /* highest colour used */
APTR ctf_PlanePick; /* plane selection */
APTR ctf_PlaneOnOff; /* plane on/off defaults */
struct ColorFontColors *ctf_ColorTable;
APTR ctf_CharData[8]; /* per-plane glyph data */
};
```
---
## References
- NDK39: `diskfont/diskfont.h`
- NDK39: `diskfont/diskfont.h`, `graphics/text.h`
- ADCD 2.1: diskfont.library autodocs
- See also: [text_fonts.md](../08_graphics/text_fonts.md) — TextFont structure and rendering

View file

@ -4,18 +4,93 @@
## Overview
`expansion.library` handles automatic configuration of Zorro II/III expansion boards. At boot, the OS scans the expansion bus and assigns base addresses to each board based on its AutoConfig ROM.
`expansion.library` handles automatic configuration of Zorro II/III expansion boards. At boot, the OS scans the expansion bus and assigns base addresses to each board based on its **AutoConfig ROM** — a 256-byte structure that identifies the board's manufacturer, product, memory requirements, and bus type.
Understanding AutoConfig is essential for FPGA core development — MiSTer cores that emulate expansion hardware (RAM boards, accelerators, RTG cards) must present valid AutoConfig data to the boot ROM.
---
## Zorro Bus Architecture
```mermaid
flowchart LR
subgraph "Zorro II Address Space (8 MB)"
Z2["$200000$9FFFFF"]
Z2A["Board A: $200000 (2 MB)"]
Z2B["Board B: $400000 (512 KB)"]
Z2C["Board C: $480000 (64 KB)"]
end
subgraph "Zorro III Address Space (1.75 GB)"
Z3["$10000000$7FFFFFFF"]
Z3A["Board D: $40000000 (16 MB)"]
end
CPU["68020/030/040"] --> Z2
CPU --> Z3
```
| Feature | Zorro II | Zorro III |
|---|---|---|
| Bus width | 16-bit | 32-bit |
| Address space | $200000$9FFFFF (8 MB) | $10000000$7FFFFFFF |
| Max board size | 8 MB | 1 GB |
| Burst transfer | No | Yes (37 MB/s peak) |
| Auto-sizing | No | Yes (dynamic) |
| DMA capable | Yes | Yes |
| Systems | A2000, A500, A1200 | A3000, A4000 |
---
## AutoConfig Sequence
1. Board asserts `CFGIN` to request configuration
2. CPU reads 256-byte config area at `$E80000`
3. Board reports: manufacturer ID, product ID, board size, flags
4. OS assigns a base address via `WriteExpansionByte`
5. Board relocates to assigned address
6. Next board in chain is configured
The AutoConfig mechanism runs during early boot, before DOS is loaded:
```mermaid
sequenceDiagram
participant ROM as Kickstart ROM
participant BUS as Zorro Bus
participant BOARD as Expansion Board
ROM->>BUS: Assert CFGOUT chain
BOARD->>BUS: Assert CFGIN (ready to configure)
ROM->>BOARD: Read config area at $E80000
Note over ROM: Parse manufacturer, product, size, type
ROM->>BOARD: Write assigned base address
Note over BOARD: Board relocates to assigned address
ROM->>BUS: Pass CFGIN to next board
Note over ROM: Repeat until no more boards respond
```
### AutoConfig ROM Layout ($E80000)
The board presents its identity at the configuration address. Each byte is read from **even** addresses only ($E80000, $E80002, $E80004...):
| Offset | Register | Bits | Description |
|---|---|---|---|
| $00 | `er_Type` | 7:6 | Board type: 11=Zorro III, 10=Zorro II |
| $00 | `er_Type` | 5 | Memory board (1) or I/O board (0) |
| $00 | `er_Type` | 4 | Chain bit — more boards follow |
| $00 | `er_Type` | 3:0 | Size code (see table below) |
| $02 | `er_Product` | 7:0 | Product number (0255) |
| $04 | `er_Flags` | 7 | Can be shut up (mapped out) |
| $04 | `er_Flags` | 5 | Board's memory is free (add to system pool) |
| $08/$0A | `er_Manufacturer` | 15:0 | Manufacturer ID (IANA-like registry) |
| $0C$12 | `er_SerialNumber` | 31:0 | Serial number (unique per board) |
| $20/$22 | `er_InitDiagVec` | 15:0 | Offset to optional boot ROM (DiagArea) |
### Size Code
| Code | Zorro II Size | Zorro III Size |
|---|---|---|
| $0 | 8 MB | 16 MB |
| $1 | 64 KB | 32 MB |
| $2 | 128 KB | 64 MB |
| $3 | 256 KB | 128 MB |
| $4 | 512 KB | 256 MB |
| $5 | 1 MB | 512 MB |
| $6 | 2 MB | 1 GB |
| $7 | 4 MB | — |
---
@ -25,27 +100,27 @@
/* libraries/configvars.h — NDK39 */
struct ConfigDev {
struct Node cd_Node;
UBYTE cd_Flags; /* CDF_* flags */
UBYTE cd_Flags; /* CDF_CONFIGME, CDF_BADMEMORY, etc. */
UBYTE cd_Pad;
struct ExpansionRom cd_Rom; /* AutoConfig ROM data */
struct ExpansionRom cd_Rom; /* AutoConfig ROM data (copied) */
APTR cd_BoardAddr; /* assigned base address */
ULONG cd_BoardSize; /* board size in bytes */
UWORD cd_SlotAddr; /* slot address */
UWORD cd_SlotSize;
APTR cd_Driver; /* driver bound to this board */
struct ConfigDev *cd_NextCD; /* next in chain */
/* ... */
};
struct ExpansionRom {
UBYTE er_Type; /* board type + size code */
UBYTE er_Product; /* product number */
UBYTE er_Flags;
UBYTE er_Product; /* product number (0255) */
UBYTE er_Flags; /* can shut up, has memory, etc. */
UBYTE er_Reserved03;
UWORD er_Manufacturer; /* manufacturer ID */
ULONG er_SerialNumber;
UWORD er_InitDiagVec; /* boot ROM offset */
/* ... */
UWORD er_Manufacturer; /* manufacturer ID (16-bit) */
ULONG er_SerialNumber; /* board serial number */
UWORD er_InitDiagVec; /* offset to DiagArea boot ROM */
APTR er_Reserved0c;
APTR er_Reserved10;
};
```
@ -57,9 +132,24 @@ struct ExpansionRom {
struct Library *ExpansionBase = OpenLibrary("expansion.library", 0);
struct ConfigDev *cd = NULL;
while ((cd = FindConfigDev(cd, 2167, 11))) {
/* manufacturer=2167 (Individual Computers), product=11 (ACA500plus) */
Printf("Board at $%08lx, size %lu\n", cd->cd_BoardAddr, cd->cd_BoardSize);
/* Find all boards from a specific manufacturer+product: */
while ((cd = FindConfigDev(cd, 2167, 11)))
{
Printf("Board at $%08lx, size %lu bytes\n",
cd->cd_BoardAddr, cd->cd_BoardSize);
Printf(" Manufacturer: %u, Product: %u\n",
cd->cd_Rom.er_Manufacturer, cd->cd_Rom.er_Product);
}
/* Find ANY board: use -1 for wildcard */
cd = NULL;
while ((cd = FindConfigDev(cd, -1, -1)))
{
Printf("Mfr=%u Prod=%u at $%08lx (%lu bytes)\n",
cd->cd_Rom.er_Manufacturer,
cd->cd_Rom.er_Product,
cd->cd_BoardAddr,
cd->cd_BoardSize);
}
```
@ -67,12 +157,54 @@ while ((cd = FindConfigDev(cd, 2167, 11))) {
## Common Manufacturer IDs
| ID | Manufacturer |
| ID | Manufacturer | Notable Products |
|---|---|---|
| 514 | Commodore | A2091 SCSI, A2065 Ethernet, A2232 serial |
| 1030 | Supra | SupraRAM, SupraDrive |
| 2017 | GVP | Impact A2000, Series II SCSI+RAM |
| 2167 | Individual Computers | Buddha, ACA500+, ACA1233 |
| 2168 | Kupke | Golem RAM |
| 4096 | University of Lowell | — |
| 4626 | ACT | Apollo accelerators |
| 4754 | MacroSystem | Retina, Warp Engine |
| 8512 | Phase5 | CyberStorm, Blizzard, CyberVision |
| 12802 | Village Tronic | Picasso II, Picasso IV |
---
## DiagArea — Boot ROMs on Expansion Boards
If `er_InitDiagVec` is non-zero, the board has a boot ROM that runs during expansion configuration:
```c
struct DiagArea {
UBYTE da_Config; /* DAC_WORDWIDE, DAC_BYTEWIDE, etc. */
UBYTE da_Flags; /* DAC_CONFIGTIME or DAC_BINDTIME */
UWORD da_Size; /* total size of DiagArea */
UWORD da_DiagPoint; /* offset to diagnostic routine */
UWORD da_BootPoint; /* offset to boot code */
char da_Name[]; /* optional handler name */
};
```
Boot ROMs are used by:
- SCSI controllers (to boot from hard disk)
- Network cards (to boot via TFTP)
- Accelerator boards (to patch CPU-specific features)
---
## FPGA Implementation Notes
For MiSTer or other FPGA cores emulating Zorro expansion:
| Aspect | Requirement |
|---|---|
| 514 | Commodore |
| 2017 | GVP |
| 2167 | Individual Computers |
| 8512 | Phase5 |
| AutoConfig ROM | Must present valid er_Type, er_Product, er_Manufacturer at $E80000 |
| Address assignment | Must accept base address write and relocate accordingly |
| CFGIN/CFGOUT chain | Must implement daisy-chain protocol for multi-board configs |
| Memory type flag | Set `er_Flags` bit 5 if board memory should be added to system pool |
| Shut-up | Board must go silent after configuration (stop responding at $E80000) |
---
@ -80,3 +212,6 @@ while ((cd = FindConfigDev(cd, 2167, 11))) {
- NDK39: `libraries/configvars.h`, `libraries/expansion.h`
- ADCD 2.1: expansion.library autodocs
- Dave Haynie: *"The Amiga Zorro III Bus Specification"* — definitive bus reference
- See also: [rtg_driver.md](../16_driver_development/rtg_driver.md) — RTG cards use Zorro AutoConfig
- See also: [device_driver_basics.md](../16_driver_development/device_driver_basics.md) — driver binding to ConfigDev

View file

@ -4,7 +4,28 @@
## Overview
Every Workbench-visible file has a companion `.info` file containing its icon imagery, tooltypes, and default tool. `icon.library` provides reading/writing these structures.
Every Workbench-visible file has a companion `.info` file containing its icon imagery, tool types (key=value metadata), default tool, stack size, and position. `icon.library` provides reading, writing, and manipulating these structures.
The `.info` file format is binary, not text. icon.library handles all serialisation.
```mermaid
flowchart LR
subgraph "Disk"
FILE["myapp"]
INFO["myapp.info"]
end
subgraph "icon.library"
GET["GetDiskObject"] --> DO["struct DiskObject"]
DO --> IMG["Icon imagery<br/>(selected + unselected)"]
DO --> TT["ToolTypes<br/>(key=value strings)"]
DO --> DT["DefaultTool<br/>(path to handler)"]
DO --> POS["Position<br/>(x, y on Workbench)"]
end
INFO --> GET
style DO fill:#e8f4fd,stroke:#2196f3,color:#333
```
---
@ -14,16 +35,16 @@ Every Workbench-visible file has a companion `.info` file containing its icon im
/* workbench/workbench.h — NDK39 */
struct DiskObject {
UWORD do_Magic; /* $E310 = WB_DISKMAGIC */
UWORD do_Version; /* WB_DISKVERSION */
struct Gadget do_Gadget; /* the icon gadget */
UBYTE do_Type; /* WBDISK, WBTOOL, WBPROJECT, etc. */
UWORD do_Version; /* WB_DISKVERSION (1) */
struct Gadget do_Gadget; /* the icon gadget (contains imagery) */
UBYTE do_Type; /* icon type: WBDISK, WBTOOL, etc. */
char *do_DefaultTool; /* default tool path */
char **do_ToolTypes; /* NULL-terminated string array */
LONG do_CurrentX; /* icon X position */
LONG do_CurrentX; /* icon X position on Workbench */
LONG do_CurrentY; /* icon Y position */
BPTR do_DrawerData; /* drawer window position (BPTR) */
char *do_ToolWindow; /* tool window spec */
LONG do_StackSize; /* stack size for tool */
char *do_ToolWindow; /* tool window spec (CON: string) */
LONG do_StackSize; /* stack size for tool launch */
};
```
@ -31,36 +52,137 @@ struct DiskObject {
## Icon Types
| Constant | Value | Description |
|---|---|---|
| `WBDISK` | 1 | Disk/volume icon |
| `WBDRAWER` | 2 | Drawer (directory) |
| `WBTOOL` | 3 | Executable tool |
| `WBPROJECT` | 4 | Project (document) |
| `WBGARBAGE` | 5 | Trashcan |
| `WBDEVICE` | 6 | Device |
| `WBKICK` | 7 | Kickstart disk |
| `WBAPPICON` | 8 | AppIcon (OS 2.0+) |
| Constant | Value | Description | Workbench Behaviour |
|---|---|---|---|
| `WBDISK` | 1 | Disk/volume icon | Opens drawer showing disk contents |
| `WBDRAWER` | 2 | Drawer (directory) | Opens drawer window |
| `WBTOOL` | 3 | Executable tool | Launches the program |
| `WBPROJECT` | 4 | Project (document) | Launches `do_DefaultTool` with this file as argument |
| `WBGARBAGE` | 5 | Trashcan | Special drawer for deleted files |
| `WBDEVICE` | 6 | Device | Shown in Workbench root |
| `WBKICK` | 7 | Kickstart disk | ROM update disk |
| `WBAPPICON` | 8 | AppIcon (OS 2.0+) | Application-registered dynamic icon |
---
## ToolTypes
ToolTypes are key=value strings stored in `do_ToolTypes`:
## Reading Icons
```c
struct Library *IconBase = OpenLibrary("icon.library", 0);
/* Load a DiskObject from disk: */
struct DiskObject *dobj = GetDiskObject("SYS:Utilities/MultiView");
if (dobj)
{
Printf("Type: %ld\n", dobj->do_Type);
Printf("Default tool: %s\n", dobj->do_DefaultTool ?
dobj->do_DefaultTool : "(none)");
Printf("Stack: %ld bytes\n", dobj->do_StackSize);
FreeDiskObject(dobj);
}
/* Get the default icon for a file type: */
struct DiskObject *defIcon = GetDefDiskObject(WBPROJECT);
/* ... use defIcon ... */
FreeDiskObject(defIcon);
```
---
## ToolTypes — Key/Value Metadata
ToolTypes are the Amiga's equivalent of application-specific metadata. They're stored as a NULL-terminated array of strings in the `.info` file.
```c
/* Example .info file ToolTypes:
PUBSCREEN=Workbench
NOBLITTER=YES
DONOTWAIT
CX_PRIORITY=0
(DISABLED_OPTION)
*/
struct DiskObject *dobj = GetDiskObject("myapp");
if (dobj) {
char *val = FindToolType(dobj->do_ToolTypes, "PUBSCREEN");
if (val) Printf("Screen: %s\n", val);
if (MatchToolValue(FindToolType(dobj->do_ToolTypes, "FLAGS"), "DEBUG"))
Printf("Debug mode\n");
if (dobj)
{
/* Find a specific ToolType: */
char *screen = FindToolType(dobj->do_ToolTypes, "PUBSCREEN");
if (screen)
Printf("Public screen: %s\n", screen); /* "Workbench" */
/* Check if a ToolType exists (no value): */
if (FindToolType(dobj->do_ToolTypes, "DONOTWAIT"))
Printf("DONOTWAIT is set\n");
/* Check for a specific value within a multi-value ToolType: */
char *flags = FindToolType(dobj->do_ToolTypes, "FLAGS");
if (MatchToolValue(flags, "DEBUG"))
Printf("Debug mode enabled\n");
/* Enumerate all ToolTypes: */
char **tt = dobj->do_ToolTypes;
while (*tt)
{
Printf(" ToolType: %s\n", *tt);
tt++;
}
FreeDiskObject(dobj);
}
```
### ToolType Conventions
| Convention | Meaning | Example |
|---|---|---|
| `KEY=VALUE` | Named setting with value | `PUBSCREEN=Workbench` |
| `KEY` (no value) | Boolean flag — presence = true | `DONOTWAIT` |
| `(KEY)` | Disabled/commented out | `(CX_POPUP=NO)` |
| `KEY=val1|val2` | Multi-value (check with `MatchToolValue`) | `FLAGS=DEBUG|VERBOSE` |
---
## Writing / Modifying Icons
```c
/* Write a DiskObject to disk: */
PutDiskObject("myfile", dobj);
/* Create a new icon programmatically: */
struct DiskObject *newIcon = GetDefDiskObject(WBPROJECT);
newIcon->do_DefaultTool = "SYS:Utilities/MultiView";
newIcon->do_StackSize = 8192;
char *toolTypes[] = {
"PUBSCREEN=Workbench",
"DONOTWAIT",
NULL
};
newIcon->do_ToolTypes = toolTypes;
PutDiskObject("myfile", newIcon);
FreeDiskObject(newIcon);
```
---
## OS 3.5+ New-Style Icons
AmigaOS 3.5 introduced **true-colour icons** (PNG-based) alongside the legacy planar format. The `icon.library` v46+ handles both transparently — `GetDiskObject` returns the best available format.
| Feature | Legacy (OS 1.x3.1) | New-Style (OS 3.5+) |
|---|---|---|
| Format | Planar bitplane imagery | PNG/true-colour embedded |
| Colours | 416 (Workbench palette) | 24-bit true colour |
| Size | Fixed (standard sizes) | Scalable |
| Transparency | 1-bit mask | 8-bit alpha channel |
| Storage | `do_Gadget.GadgetRender` | Extended chunks in `.info` |
---
## References
- NDK39: `workbench/workbench.h`, `workbench/icon.h`
- ADCD 2.1: icon.library autodocs
- See also: [workbench.md](workbench.md) — Workbench integration (AppIcon, AppWindow)

View file

@ -4,53 +4,124 @@
## Overview
IFF (Interchange File Format) is EA/Commodore's universal container format. `iffparse.library` provides stream-oriented parsing/writing of IFF files. Common IFF types: ILBM (images), 8SVX (audio), ANIM (animation), FTXT (formatted text).
IFF (Interchange File Format) is EA/Commodore's universal container format used throughout the Amiga ecosystem. `iffparse.library` provides stream-oriented parsing and writing of IFF files, handling the nested chunk structure, byte ordering, and padding automatically.
Common IFF types:
- **ILBM** — Interleaved Bitmap (images)
- **8SVX** — 8-bit sampled voice (audio)
- **ANIM** — Animation sequences
- **FTXT** — Formatted text
```mermaid
flowchart TD
subgraph "IFF ILBM File"
FORM["FORM ILBM"] --> BMHD["BMHD<br/>(bitmap header)"]
FORM --> CMAP["CMAP<br/>(colour palette)"]
FORM --> CAMG["CAMG<br/>(Amiga view mode)"]
FORM --> BODY["BODY<br/>(pixel data)"]
end
subgraph "iffparse.library"
IH["AllocIFF"] --> INIT["InitIFFasDOS"]
INIT --> OPEN["OpenIFF(READ)"]
OPEN --> PARSE["ParseIFF(SCAN)"]
PARSE --> READ["ReadChunkBytes"]
READ --> CLOSE["CloseIFF"]
end
FORM -.-> PARSE
style FORM fill:#e8f4fd,stroke:#2196f3,color:#333
```
---
## IFF Structure
All IFF files follow a nested chunk structure. Everything is **big-endian** (network byte order):
```
FORM <size> <type>
<chunk_id> <size> <data...>
<chunk_id> <size> <data...>
...
FORM <size:LONG> <type:4 chars>
<chunk_id:4 chars> <size:LONG> <data...> [pad byte if odd size]
<chunk_id:4 chars> <size:LONG> <data...> [pad byte]
...
Nested FORMs:
LIST <size> <type>
FORM <size> <type>
...
FORM <size> <type>
...
```
All values are big-endian. Chunks are padded to even byte boundaries.
| Container | Purpose |
|---|---|
| `FORM` | A single structured data object |
| `LIST` | Ordered collection of FORMs |
| `CAT ` | Unordered concatenation of FORMs |
| `PROP` | Default property block (within LIST) |
---
## Key Functions
## Reading an IFF File
```c
struct Library *IFFParseBase = OpenLibrary("iffparse.library", 0);
struct IFFHandle *iff = AllocIFF();
/* Open from file: */
/* Open from AmigaDOS file: */
iff->iff_Stream = (ULONG)Open("image.iff", MODE_OLDFILE);
InitIFFasDOS(iff);
OpenIFF(iff, IFFF_READ);
if (!iff->iff_Stream) { /* error */ }
InitIFFasDOS(iff); /* use DOS Read/Write/Seek hooks */
if (OpenIFF(iff, IFFF_READ)) { /* error */ }
/* Register chunks we want to stop at: */
StopChunk(iff, ID_ILBM, ID_BMHD);
StopChunk(iff, ID_ILBM, ID_BODY);
StopChunk(iff, ID_ILBM, ID_CMAP);
StopChunk(iff, ID_ILBM, ID_CAMG);
StopChunk(iff, ID_ILBM, ID_BODY);
/* Parse: */
/* Parse — stops at each registered chunk: */
LONG error;
while ((error = ParseIFF(iff, IFFPARSE_SCAN)) == 0) {
while ((error = ParseIFF(iff, IFFPARSE_SCAN)) == 0)
{
struct ContextNode *cn = CurrentChunk(iff);
switch (cn->cn_ID) {
switch (cn->cn_ID)
{
case ID_BMHD:
{
struct BitMapHeader bmhd;
ReadChunkBytes(iff, &bmhd, sizeof(bmhd));
Printf("Image: %ldx%ld, %ld planes\n",
bmhd.bmh_Width, bmhd.bmh_Height, bmhd.bmh_Depth);
break;
}
case ID_CMAP:
ReadChunkBytes(iff, palette, cn->cn_Size);
{
UBYTE palette[256 * 3];
LONG palSize = ReadChunkBytes(iff, palette, cn->cn_Size);
LONG numColours = palSize / 3;
Printf("Palette: %ld colours\n", numColours);
break;
}
case ID_CAMG:
{
ULONG viewMode;
ReadChunkBytes(iff, &viewMode, 4);
if (viewMode & HAM) Printf("HAM mode\n");
break;
}
case ID_BODY:
ReadChunkBytes(iff, bodydata, cn->cn_Size);
{
/* Read pixel data (may be compressed) */
UBYTE *bodyData = AllocMem(cn->cn_Size, MEMF_ANY);
ReadChunkBytes(iff, bodyData, cn->cn_Size);
/* ... decompress if bmhd.bmh_Compression == 1 (ByteRun1) ... */
FreeMem(bodyData, cn->cn_Size);
break;
}
}
}
@ -61,21 +132,140 @@ FreeIFF(iff);
---
## Writing an IFF File
```c
struct IFFHandle *iff = AllocIFF();
iff->iff_Stream = (ULONG)Open("output.iff", MODE_NEWFILE);
InitIFFasDOS(iff);
OpenIFF(iff, IFFF_WRITE);
/* Start the FORM: */
PushChunk(iff, ID_ILBM, ID_FORM, IFFSIZE_UNKNOWN);
/* Write BMHD chunk: */
PushChunk(iff, 0, ID_BMHD, sizeof(struct BitMapHeader));
WriteChunkBytes(iff, &bmhd, sizeof(bmhd));
PopChunk(iff);
/* Write CMAP chunk: */
PushChunk(iff, 0, ID_CMAP, numColours * 3);
WriteChunkBytes(iff, palette, numColours * 3);
PopChunk(iff);
/* Write BODY chunk: */
PushChunk(iff, 0, ID_BODY, IFFSIZE_UNKNOWN);
WriteChunkBytes(iff, bodyData, bodySize);
PopChunk(iff);
/* Close the FORM: */
PopChunk(iff);
CloseIFF(iff);
Close((BPTR)iff->iff_Stream);
FreeIFF(iff);
```
---
## ILBM BitMapHeader
```c
struct BitMapHeader {
UWORD bmh_Width; /* image width in pixels */
UWORD bmh_Height; /* image height in pixels */
WORD bmh_Left; /* x offset (usually 0) */
WORD bmh_Top; /* y offset (usually 0) */
UBYTE bmh_Depth; /* number of bitplanes */
UBYTE bmh_Masking; /* 0=none, 1=hasMask, 2=hasTransparentColor */
UBYTE bmh_Compression; /* 0=none, 1=ByteRun1 */
UBYTE bmh_Pad;
UWORD bmh_Transparent; /* transparent colour index */
UBYTE bmh_XAspect; /* pixel aspect ratio */
UBYTE bmh_YAspect;
WORD bmh_PageWidth; /* source page width */
WORD bmh_PageHeight; /* source page height */
};
```
---
## ByteRun1 Compression
ILBM BODY data is typically compressed with **ByteRun1** (a simple RLE):
```
For each byte n:
0..127: copy next n+1 bytes literally
-1..-127: repeat next byte (-n+1) times
-128: no-op (skip)
```
```c
/* Decompress ByteRun1: */
void DecompressByteRun1(UBYTE *src, UBYTE *dst, LONG dstSize)
{
UBYTE *end = dst + dstSize;
while (dst < end)
{
BYTE n = *src++;
if (n >= 0)
{
LONG count = n + 1;
memcpy(dst, src, count);
src += count;
dst += count;
}
else if (n != -128)
{
LONG count = -n + 1;
memset(dst, *src++, count);
dst += count;
}
}
}
```
---
## Common Chunk IDs
| FORM Type | Chunk | Description |
|---|---|---|
| `ILBM` | `BMHD` | Bitmap header (width, height, depth) |
| `ILBM` | `CMAP` | Colour map (R,G,B triples) |
| `ILBM` | `BODY` | Image body data |
| `ILBM` | `CAMG` | Amiga display mode |
| `8SVX` | `VHDR` | Voice header |
| `8SVX` | `BODY` | Audio sample data |
| `ANIM` | `ANHD` | Animation header |
| `ANIM` | `DLTA` | Delta frame data |
| FORM Type | Chunk | Size | Description |
|---|---|---|---|
| `ILBM` | `BMHD` | 20 | Bitmap header (width, height, depth, compression) |
| `ILBM` | `CMAP` | n×3 | Colour map (R,G,B triples, 8-bit each) |
| `ILBM` | `CAMG` | 4 | Amiga display mode (ModeID for ViewPort) |
| `ILBM` | `BODY` | varies | Pixel data (interleaved bitplanes) |
| `ILBM` | `CRNG` | 8 | Colour cycling range (DPaint) |
| `ILBM` | `GRAB` | 4 | Hotspot (cursor/brush grab point) |
| `8SVX` | `VHDR` | 20 | Voice header (rate, volume, octaves) |
| `8SVX` | `BODY` | varies | Audio sample data (signed 8-bit) |
| `ANIM` | `ANHD` | 24 | Animation frame header |
| `ANIM` | `DLTA` | varies | Delta-compressed frame data |
| `FTXT` | `CHRS` | varies | Character string data |
---
## Using IFF with Clipboard
```c
/* Read from clipboard instead of file: */
struct IFFHandle *iff = AllocIFF();
struct ClipboardHandle *ch = OpenClipboard(PRIMARY_CLIP);
iff->iff_Stream = (ULONG)ch;
InitIFFasClip(iff); /* use clipboard hooks instead of DOS */
OpenIFF(iff, IFFF_READ);
/* ... parse as normal ... */
CloseIFF(iff);
CloseClipboard(ch);
FreeIFF(iff);
```
---
## References
- NDK39: `libraries/iffparse.h`, `datatypes/pictureclass.h`
- EA IFF-85 specification: the original format definition
- ADCD 2.1: iffparse.library autodocs
- See also: [ham_ehb_modes.md](../08_graphics/ham_ehb_modes.md) — HAM-encoded ILBM files

View file

@ -4,21 +4,41 @@
## Overview
`keymap.library` translates raw keycodes from `keyboard.device` into character codes using the active keymap. Each keymap defines the mapping from physical keys to characters, including dead keys and string sequences.
`keymap.library` translates **raw keycodes** from `keyboard.device` into character codes using the active keymap. The Amiga keyboard generates hardware scancodes (0x000x77); the keymap defines how each physical key maps to characters, including shifted variants, dead keys (accented characters), and string sequences (function keys).
```mermaid
flowchart LR
KB["Physical Keyboard"] -->|"Raw scancode<br/>(0x00-0x77)"| KBD["keyboard.device"]
KBD -->|"InputEvent<br/>(rawkey + qualifiers)"| KM["keymap.library<br/>MapRawKey"]
KM -->|"ASCII / ANSI<br/>character(s)"| APP["Application<br/>or console.device"]
style KM fill:#e8f4fd,stroke:#2196f3,color:#333
```
---
## Key Functions
```c
/* Map a raw keycode + qualifiers to ASCII: */
LONG actual = MapRawKey(&inputEvent, buffer, bufsize, NULL);
/* Returns number of characters, -1 if buffer too small */
#include <devices/inputevent.h>
#include <libraries/keymap.h>
/* Map ANSI code back to raw key: */
WORD MapANSI(STRPTR string, WORD count,
STRPTR buffer, WORD length,
struct KeyMap *keyMap);
/* Map a raw keycode + qualifiers to characters: */
struct InputEvent ie;
ie.ie_Class = IECLASS_RAWKEY;
ie.ie_Code = rawKeyCode; /* 0x000x77 */
ie.ie_Qualifier = qualifiers; /* IEQUALIFIER_LSHIFT, etc. */
ie.ie_EventAddress = NULL;
char buffer[16];
LONG numChars = MapRawKey(&ie, buffer, sizeof(buffer), NULL);
/* Returns number of characters, or -1 if buffer too small */
/* numChars=0 means the key doesn't produce a character (e.g., Shift alone) */
/* Map ANSI characters back to raw key events: */
WORD result = MapANSI(string, numChars,
outBuffer, outLength,
NULL); /* NULL = use default keymap */
```
---
@ -28,19 +48,115 @@ WORD MapANSI(STRPTR string, WORD count,
```c
/* devices/keymap.h — NDK39 */
struct KeyMap {
UBYTE *km_LoKeyMapTypes; /* type array for keys 0x000x3F */
ULONG *km_LoKeyMap; /* mapping array for keys 0x000x3F */
UBYTE *km_LoCapsable; /* caps-lock bitmap */
UBYTE *km_LoKeyMapTypes; /* type byte per key, 0x000x3F */
ULONG *km_LoKeyMap; /* mapping data per key, 0x000x3F */
UBYTE *km_LoCapsable; /* caps-lock bitmap (1 bit per key) */
UBYTE *km_LoRepeatable; /* auto-repeat bitmap */
UBYTE *km_HiKeyMapTypes; /* type array for keys 0x400x77 */
ULONG *km_HiKeyMap;
UBYTE *km_HiKeyMapTypes; /* type byte per key, 0x400x77 */
ULONG *km_HiKeyMap; /* mapping data per key, 0x400x77 */
UBYTE *km_HiCapsable;
UBYTE *km_HiRepeatable;
};
```
The keymap is split into two halves:
- **Lo keys** (0x000x3F): main keyboard area (letters, numbers, symbols)
- **Hi keys** (0x400x77): function keys, cursor keys, keypad, special keys
### Key Type Flags
| Flag | Meaning |
|---|---|
| `KCF_SHIFT` | Key has shifted variant |
| `KCF_ALT` | Key has Alt variant |
| `KCF_CONTROL` | Key has Control variant |
| `KCF_DEAD` | Dead key (accent prefix — combines with next keypress) |
| `KCF_STRING` | Key produces a multi-byte string (e.g., function keys → escape sequences) |
| `KCF_NOP` | Key produces no character |
---
## Raw Keycodes
Key physical positions are fixed across all Amiga keyboards:
| Rawkey | Key | Rawkey | Key |
|---|---|---|---|
| `0x00` | \` (backtick) | `0x40` | Space |
| `0x01` | 1 | `0x41` | Backspace |
| `0x02` | 2 | `0x42` | Tab |
| `0x03``0x0A` | 30 | `0x43` | Enter (keypad) |
| `0x10``0x19` | QP row | `0x44` | Return |
| `0x20``0x28` | AL row | `0x45` | Escape |
| `0x31``0x39` | Z/ row | `0x46` | Delete |
| `0x30` | — | `0x4C` | Cursor Up |
| | | `0x4D` | Cursor Down |
| | | `0x4E` | Cursor Right |
| | | `0x4F` | Cursor Left |
| `0x50``0x59` | F1F10 | `0x60` | Left Shift |
| | | `0x61` | Right Shift |
| | | `0x63` | Control |
| | | `0x64` | Left Alt |
| | | `0x65` | Right Alt |
| | | `0x66` | Left Amiga |
| | | `0x67` | Right Amiga |
> [!NOTE]
> **Key-up events** have bit 7 set: rawkey `0x80 | keycode`. So rawkey `0xC5` = Escape key released.
---
## Dead Keys (Accented Characters)
Dead keys work in two presses:
1. Press the dead key (e.g., Alt+H = acute accent ´)
2. Press the base letter (e.g., e)
3. Result: é
```c
/* Dead key handling is automatic in MapRawKey if you pass
the previous dead key's InputEvent via ie_EventAddress: */
struct InputEvent deadEvent;
/* ... first keypress (dead key) stored here ... */
ie.ie_EventAddress = (APTR)&deadEvent; /* chain to dead key */
numChars = MapRawKey(&ie, buffer, sizeof(buffer), NULL);
/* buffer now contains the composed character */
```
---
## Changing Keymaps
```c
/* Set a different keymap: */
struct KeyMap *germanMap;
/* Load from DEVS:Keymaps/d (German layout) */
/* System-wide change via Preferences: */
/* Use the Input prefs editor, or: */
SetKeyMapDefault(newKeyMap);
```
Available keymaps in `DEVS:Keymaps/`:
| File | Layout |
|---|---|
| `usa` | US English (QWERTY) — default |
| `gb` | British English |
| `d` | German (QWERTZ) |
| `f` | French (AZERTY) |
| `i` | Italian |
| `e` | Spanish |
| `dk` | Danish |
| `s` | Swedish |
| `n` | Norwegian |
---
## References
- NDK39: `devices/keymap.h`, `libraries/keymap.h`
- ADCD 2.1: keymap.library autodocs
- See also: [console.md](../10_devices/console.md) — console.device uses keymap for input
- See also: [input_events.md](../09_intuition/input_events.md) — InputEvent structure

View file

@ -1,50 +1,224 @@
[← Home](../README.md) · [Libraries](README.md)
# layers.library — Window Clipping Layers
# layers.library — Window Clipping and Damage Repair
## Overview
`layers.library` provides the clipping and damage-repair infrastructure that Intuition windows are built on. Each window's `RastPort` is backed by a `Layer` that manages overlapping regions.
`layers.library` provides the clipping and damage-repair infrastructure that Intuition windows are built on. Every window's `RastPort` is backed by a `Layer` that manages overlapping regions, damage tracking, and optional backing-store for obscured content.
When you draw to a window, all drawing operations are automatically clipped to the window's visible area by the layer system. You never draw "outside" your window or over another window — layers enforces this transparently.
```mermaid
flowchart TD
subgraph "Screen BitMap"
subgraph "Layer A (front)"
LA["Fully visible<br/>ClipRect covers<br/>entire layer"]
end
subgraph "Layer B (middle)"
LB["Partially obscured<br/>by Layer A"]
LB1["ClipRect 1<br/>(visible)"]
LB2["ClipRect 2<br/>(obscured)"]
end
subgraph "Layer C (back)"
LC["Mostly obscured"]
end
end
DRAW["Draw into Layer B"] --> CLIP["layers.library<br/>clips to visible<br/>ClipRects only"]
CLIP --> LB1
style LA fill:#c8e6c9,stroke:#2e7d32,color:#333
style LB fill:#fff9c4,stroke:#f9a825,color:#333
style LC fill:#ffcdd2,stroke:#c62828,color:#333
style CLIP fill:#e8f4fd,stroke:#2196f3,color:#333
```
---
## Layer Types
| Flag | Type | Description |
|---|---|---|
| `LAYERSIMPLE` | Simple Refresh | Application must redraw damaged areas |
| `LAYERSMART` | Smart Refresh | System saves obscured regions |
| `LAYERSUPER` | Super BitMap | Application provides full-size bitmap |
| `LAYERBACKDROP` | Backdrop | Behind all other layers |
| Flag | Type | Backing Store | Damage Handling | Memory Cost |
|---|---|---|---|---|
| `LAYERSIMPLE` | Simple Refresh | None | App must redraw on `IDCMP_REFRESHWINDOW` | Minimal |
| `LAYERSMART` | Smart Refresh | Auto — obscured regions saved/restored | Automatic — OS handles damage | Moderate |
| `LAYERSUPER` | Super BitMap | Full off-screen bitmap (app provides) | Full bitmap always valid | High |
| `LAYERBACKDROP` | Backdrop | Modifier — behind all normal layers | Depends on refresh type | — |
```mermaid
flowchart LR
subgraph "Simple Refresh"
S1["Window obscured"] --> S2["Damage region<br/>recorded"]
S2 --> S3["IDCMP_REFRESHWINDOW<br/>sent to app"]
S3 --> S4["App redraws<br/>between Begin/EndRefresh"]
end
subgraph "Smart Refresh"
M1["Window obscured"] --> M2["Obscured area<br/>saved to buffer"]
M2 --> M3["Window revealed"] --> M4["OS restores<br/>from buffer"]
end
style S3 fill:#ffcdd2,stroke:#c62828,color:#333
style M4 fill:#c8e6c9,stroke:#2e7d32,color:#333
```
---
## Key Functions
## ClipRects — The Clipping Engine
Each layer maintains a linked list of **ClipRects** — rectangles that define how each region of the layer should be handled:
```c
struct ClipRect {
struct ClipRect *Next; /* next in chain */
struct ClipRect *prev; /* previous */
struct Layer *lobs; /* layer that obscures this rect (NULL if visible) */
struct BitMap *BitMap; /* backing store bitmap (Smart Refresh) */
LONG reserved;
LONG Flags;
struct Rectangle bounds; /* x1,y1,x2,y2 of this rect */
};
```
When drawing to a partially obscured layer:
1. The drawing call (e.g., `RectFill`) enters `graphics.library`
2. Graphics detects `rp->Layer != NULL`
3. For each **visible ClipRect**, the drawing is performed clipped to that rectangle
4. For **obscured ClipRects** (Smart Refresh), drawing goes to the backing-store bitmap instead
5. The application sees nothing — clipping is fully transparent
---
## Creating Layers Directly
While Intuition normally creates layers for windows, you can create them manually for custom display systems:
```c
struct Layer_Info *li = NewLayerInfo();
struct Layer *layer = CreateUpfrontLayer(li, bitmap,
x1, y1, x2, y2, LAYERSMART, NULL);
/* Create a layer on top: */
struct Layer *frontLayer = CreateUpfrontLayer(li, screenBitMap,
10, 10, 200, 100, /* bounds: x1, y1, x2, y2 */
LAYERSMART, /* type */
NULL); /* super bitmap (NULL for non-SUPER) */
/* Lock before drawing: */
/* Create behind existing layers: */
struct Layer *backLayer = CreateBehindLayer(li, screenBitMap,
50, 50, 250, 150,
LAYERSIMPLE | LAYERBACKDROP,
NULL);
/* Get the RastPort for drawing: */
struct RastPort *rp = frontLayer->rp;
SetAPen(rp, 1);
RectFill(rp, 0, 0, 190, 90); /* coordinates relative to layer */
```
---
## Layer Operations
### Locking
**Always lock a layer before drawing** if other tasks might modify it simultaneously:
```c
LockLayer(0, layer);
/* ... draw into layer->rp ... */
/* ... safe to draw ... */
UnlockLayer(layer);
/* Move: */
/* Lock ALL layers (for bulk operations): */
LockLayers(layerInfo);
UnlockLayers(layerInfo);
/* Lock a layer for multiple operations: */
LockLayerInfo(layerInfo);
/* ... manipulate layer stack ... */
UnlockLayerInfo(layerInfo);
```
### Moving and Resizing
```c
/* Move layer by delta: */
MoveLayer(0, layer, dx, dy);
/* Resize: */
/* Resize layer by delta: */
SizeLayer(0, layer, dw, dh);
/* Cleanup: */
/* Move to front/back of stack: */
UpfrontLayer(0, layer);
BehindLayer(0, layer);
```
### Damage and Refresh
```c
/* For Simple Refresh windows — handle IDCMP_REFRESHWINDOW: */
BeginRefresh(window);
/* ... redraw damaged area only ... */
/* The ClipRect list is temporarily set to damaged regions only */
EndRefresh(window, TRUE); /* TRUE = damage fully repaired */
```
### Install Backfill Hook
```c
/* Custom backfill instead of default (clear to pen 0): */
struct Hook backfillHook;
backfillHook.h_Entry = (HOOKFUNC)MyBackfillFunc;
InstallLayerHook(layer, &backfillHook);
/* The hook is called whenever a region needs to be filled
(e.g., when a window is moved and new area is exposed) */
```
---
## Super BitMap Layers
Super BitMap layers maintain a full-sized off-screen bitmap that the application owns. The visible portion is blitted to the screen; the rest is always valid in the off-screen buffer:
```c
/* Allocate the full bitmap: */
struct BitMap *superBM = AllocBitMap(640, 480, depth,
BMF_CLEAR, NULL);
/* Create super bitmap layer: */
struct Layer *superLayer = CreateUpfrontLayer(li, screenBitMap,
0, 0, 319, 255, /* visible area on screen */
LAYERSUPER,
superBM); /* the full-size bitmap */
/* Scroll the view within the super bitmap: */
ScrollLayer(0, superLayer, dx, dy);
/* Sync super bitmap with display after drawing: */
SyncSBitMap(superLayer);
/* Copy display back to super bitmap before hiding: */
CopySBitMap(superLayer);
```
---
## Cleanup
```c
/* Remove layers in reverse order: */
DeleteLayer(0, layer);
/* Dispose the layer info: */
DisposeLayerInfo(li);
/* If using super bitmap, free it after DeleteLayer: */
FreeBitMap(superBM);
```
---
## References
- NDK39: `graphics/layers.h`, `graphics/clip.h`
- NDK39: `graphics/layers.h`, `graphics/clip.h`, `graphics/gfx.h`
- ADCD 2.1: layers.library autodocs
- See also: [rastport.md](../08_graphics/rastport.md) — drawing through layers
- See also: [screens.md](../09_intuition/screens.md) — Intuition screen/window layer management

View file

@ -4,39 +4,180 @@
## Overview
`locale.library` (OS 2.1+) provides language-aware string lookup, date/number formatting, and character classification for internationalised applications.
`locale.library` (OS 2.1+) provides the Amiga's internationalisation (i18n) framework: language-aware string lookup via catalogues, locale-sensitive date/number/currency formatting, and character classification. Applications that use locale.library display in the user's preferred language automatically.
```mermaid
flowchart TD
APP["Application"] -->|"GetCatalogStr(cat, ID, fallback)"| LOC["locale.library"]
LOC -->|"Looks up string ID"| CAT["myapp.catalog<br/>(LOCALE:Catalogs/deutsch/)"]
CAT -->|"Found"| DE["'Datei öffnen'"]
LOC -->|"Not found"| FB["Fallback:<br/>'Open File'"]
style LOC fill:#e8f4fd,stroke:#2196f3,color:#333
style CAT fill:#c8e6c9,stroke:#2e7d32,color:#333
```
---
## Core Pattern
## Catalogue System
### Creating String IDs
```c
/* Typically in a generated header (from CatComp or FlexCat): */
#define MSG_OPEN_FILE 1
#define MSG_SAVE_FILE 2
#define MSG_QUIT 3
#define MSG_ERROR_NOTFOUND 100
/* Built-in English strings (fallback): */
static const char *builtinStrings[] = {
[MSG_OPEN_FILE] = "Open File",
[MSG_SAVE_FILE] = "Save File",
[MSG_QUIT] = "Quit",
[MSG_ERROR_NOTFOUND] = "File not found",
};
```
### Using Catalogues
```c
struct Library *LocaleBase = OpenLibrary("locale.library", 38);
/* Open the application's catalogue: */
struct Catalog *cat = OpenCatalog(NULL, "myapp.catalog",
OC_BuiltInLanguage, "english",
TAG_DONE);
OC_BuiltInLanguage, (ULONG)"english",
TAG_DONE);
/* NULL for first arg = use current locale */
/* Get localised string: */
STRPTR str = GetCatalogStr(cat, MSG_HELLO, "Hello"); /* fallback */
/* Get localised string (with English fallback): */
STRPTR openStr = GetCatalogStr(cat, MSG_OPEN_FILE, "Open File");
/* Returns German "Datei öffnen" if German catalogue exists,
otherwise the fallback "Open File" */
/* Use throughout the application: */
Printf("%s\n", GetCatalogStr(cat, MSG_QUIT, "Quit"));
/* Cleanup: */
CloseCatalog(cat);
CloseLibrary(LocaleBase);
```
### Catalogue File Structure
```
LOCALE:Catalogs/deutsch/myapp.catalog ← German
LOCALE:Catalogs/français/myapp.catalog ← French
LOCALE:Catalogs/italiano/myapp.catalog ← Italian
```
Catalogues are compiled from `.cd` (catalogue description) and `.ct` (catalogue translation) files using **CatComp** or **FlexCat**:
```
; myapp.cd — catalogue description
MSG_OPEN_FILE (1//)
Open File
;
MSG_SAVE_FILE (2//)
Save File
;
```
```
; myapp_deutsch.ct — German translation
MSG_OPEN_FILE
Datei öffnen
;
MSG_SAVE_FILE
Datei speichern
;
```
---
## Locale-Aware Formatting
```c
struct Locale *loc = OpenLocale(NULL); /* user's default */
struct Locale *loc = OpenLocale(NULL); /* user's default locale */
/* Format a date: */
FormatDate(loc, "%A %e %B %Y", &datestamp, &hook);
Printf("Country: %s\n", loc->loc_CountryName);
Printf("Language: %s\n", loc->loc_PrefLanguages[0]);
Printf("Decimal: '%s'\n", loc->loc_DecimalPoint); /* "." or "," */
Printf("Grouping: '%s'\n", loc->loc_GroupSeparator); /* "," or "." */
Printf("Currency: '%s'\n", loc->loc_MonCS); /* "$", "€", "£" */
```
/* Format a number: */
/* Uses loc->loc_GroupSeparator, loc->loc_DecimalPoint */
### Date Formatting
CloseLocale(loc);
```c
/* Format a date stamp according to locale: */
struct DateStamp ds;
DateStamp(&ds);
/* FormatDate uses a hook to receive characters: */
char dateBuf[64];
int pos = 0;
/* Simple hook that fills a buffer: */
void __saveds __asm DateHookFunc(
register __a0 struct Hook *hook,
register __a1 char ch)
{
char *buf = hook->h_Data;
buf[pos++] = ch;
buf[pos] = 0;
}
struct Hook dateHook;
dateHook.h_Entry = (HOOKFUNC)DateHookFunc;
dateHook.h_Data = dateBuf;
FormatDate(loc, "%A, %e %B %Y", &ds, &dateHook);
/* Result (German locale): "Mittwoch, 23 April 2025" */
/* Result (US locale): "Wednesday, 23 April 2025" */
```
### Format Codes
| Code | Output | Example |
|---|---|---|
| `%A` | Full weekday name | "Wednesday" / "Mittwoch" |
| `%a` | Abbreviated weekday | "Wed" / "Mi" |
| `%B` | Full month name | "April" |
| `%b` | Abbreviated month | "Apr" |
| `%d` | Day (0131) | "23" |
| `%e` | Day (131, no leading zero) | "23" |
| `%H` | Hour (0023) | "14" |
| `%I` | Hour (0112) | "02" |
| `%M` | Minute (0059) | "30" |
| `%S` | Second (0059) | "00" |
| `%p` | AM/PM | "PM" |
| `%Y` | 4-digit year | "2025" |
| `%y` | 2-digit year | "25" |
---
## Character Classification
```c
/* Locale-aware character checks: */
if (IsAlpha(loc, ch)) /* alphabetic (language-aware) */
if (IsUpper(loc, ch)) /* uppercase */
if (IsLower(loc, ch)) /* lowercase */
if (IsDigit(loc, ch)) /* digit */
if (IsAlNum(loc, ch)) /* alphanumeric */
if (IsPunct(loc, ch)) /* punctuation */
if (IsSpace(loc, ch)) /* whitespace */
/* Locale-aware case conversion: */
char upper = ConvToUpper(loc, ch);
char lower = ConvToLower(loc, ch);
/* Locale-aware string comparison: */
LONG result = StrnCmp(loc, str1, str2, -1, SC_COLLATE2);
/* SC_ASCII = byte comparison */
/* SC_COLLATE1 = primary collation (ignores accents) */
/* SC_COLLATE2 = full collation */
```
---
@ -44,3 +185,5 @@ CloseLocale(loc);
## References
- NDK39: `libraries/locale.h`
- ADCD 2.1: locale.library autodocs
- CatComp / FlexCat documentation for catalogue compilation

View file

@ -1,32 +1,87 @@
[← Home](../README.md) · [Libraries](README.md)
# rexxsyslib.library — ARexx Interface
# rexxsyslib.library — ARexx Scripting Interface
## Overview
ARexx is the Amiga's built-in macro/scripting language (based on REXX). `rexxsyslib.library` provides APIs for hosting ARexx ports, sending commands, and receiving results.
ARexx is the Amiga's built-in macro/scripting language (based on IBM's REXX). Any application can host an **ARexx port** to receive commands from scripts and other applications — this is the Amiga's primary inter-application communication mechanism for user-facing scripting.
```mermaid
flowchart LR
SCRIPT["ARexx Script<br/>(text file)"] -->|"Sends command<br/>to named port"| PORT["Application<br/>ARexx Port<br/>(e.g., 'MYAPP')"]
PORT -->|"Parses command<br/>returns result"| SCRIPT
APP2["Other Application"] -->|"FindPort + PutMsg"| PORT
style PORT fill:#e8f4fd,stroke:#2196f3,color:#333
```
---
## Adding an ARexx Port
## Adding an ARexx Port to Your Application
```c
struct MsgPort *arexxPort = CreateMsgPort();
arexxPort->mp_Node.ln_Name = "MYAPP";
arexxPort->mp_Node.ln_Pri = 0;
AddPort(arexxPort);
#include <rexx/storage.h>
#include <rexx/rxslib.h>
/* In event loop, check for ARexx messages: */
struct RexxMsg *rmsg;
while ((rmsg = (struct RexxMsg *)GetMsg(arexxPort))) {
STRPTR cmd = ARG0(rmsg); /* the command string */
/* Parse and execute command... */
rmsg->rm_Result1 = 0; /* RC = 0 (success) */
rmsg->rm_Result2 = 0;
ReplyMsg((struct Message *)rmsg);
struct MsgPort *arexxPort = CreateMsgPort();
arexxPort->mp_Node.ln_Name = "MYAPP"; /* public port name */
arexxPort->mp_Node.ln_Pri = 0;
AddPort(arexxPort); /* make it publicly findable */
/* In your main event loop, combine with IDCMP: */
ULONG arexxSig = 1 << arexxPort->mp_SigBit;
ULONG idcmpSig = 1 << window->UserPort->mp_SigBit;
ULONG sigs = Wait(arexxSig | idcmpSig);
if (sigs & arexxSig)
{
struct RexxMsg *rmsg;
while ((rmsg = (struct RexxMsg *)GetMsg(arexxPort)))
{
STRPTR cmd = ARG0(rmsg); /* the command string */
/* Parse and execute: */
if (stricmp(cmd, "QUIT") == 0)
{
rmsg->rm_Result1 = 0; /* RC = 0 (success) */
rmsg->rm_Result2 = 0;
running = FALSE;
}
else if (strnicmp(cmd, "OPEN ", 5) == 0)
{
char *filename = cmd + 5;
LONG rc = OpenFile(filename);
rmsg->rm_Result1 = rc ? 0 : 10; /* 0=ok, 10=error */
rmsg->rm_Result2 = 0;
}
else if (stricmp(cmd, "VERSION") == 0)
{
rmsg->rm_Result1 = 0;
/* Return a string result: */
if (rmsg->rm_Action & RXFF_RESULT)
rmsg->rm_Result2 = (LONG)CreateArgstring("1.0", 3);
}
else
{
rmsg->rm_Result1 = 5; /* RC_WARN — unknown command */
rmsg->rm_Result2 = 0;
}
ReplyMsg((struct Message *)rmsg);
}
}
/* Cleanup: */
RemPort(arexxPort);
/* Drain remaining messages: */
struct RexxMsg *rmsg;
while ((rmsg = (struct RexxMsg *)GetMsg(arexxPort)))
{
rmsg->rm_Result1 = 20; /* RC_FATAL */
ReplyMsg((struct Message *)rmsg);
}
DeleteMsgPort(arexxPort);
```
@ -35,17 +90,34 @@ DeleteMsgPort(arexxPort);
## Sending ARexx Commands
```c
struct MsgPort *replyPort = CreateMsgPort();
struct RexxMsg *rmsg = CreateRexxMsg(replyPort, NULL, NULL);
rmsg->rm_Args[0] = CreateArgstring("QUIT", 4);
rmsg->rm_Action = RXCOMM;
struct Library *RexxSysBase = OpenLibrary("rexxsyslib.library", 0);
struct MsgPort *replyPort = CreateMsgPort();
struct RexxMsg *rmsg = CreateRexxMsg(replyPort, NULL, "MYAPP");
rmsg->rm_Args[0] = (STRPTR)CreateArgstring("OPEN test.txt", 13);
rmsg->rm_Action = RXCOMM | RXFF_RESULT;
/* Find the target application's port: */
Forbid();
struct MsgPort *target = FindPort("TARGETAPP");
if (target) {
if (target)
{
PutMsg(target, &rmsg->rm_Node);
Permit();
WaitPort(replyPort);
GetMsg(replyPort);
/* rmsg->rm_Result1 = return code */
Printf("Return code: %ld\n", rmsg->rm_Result1);
if (rmsg->rm_Result1 == 0 && rmsg->rm_Result2)
{
Printf("Result: %s\n", (char *)rmsg->rm_Result2);
DeleteArgstring((STRPTR)rmsg->rm_Result2);
}
}
else
{
Permit();
Printf("Target port not found\n");
}
DeleteArgstring(rmsg->rm_Args[0]);
@ -55,6 +127,50 @@ DeleteMsgPort(replyPort);
---
## ARexx Script Example
```rexx
/* MyScript.rexx — control MYAPP from ARexx */
ADDRESS 'MYAPP'
OPEN 'work:data/image.iff'
IF RC > 0 THEN DO
SAY 'Failed to open file, RC=' RC
EXIT 10
END
VERSION
SAY 'App version:' RESULT
QUIT
```
---
## Return Codes
| Value | Constant | Meaning |
|---|---|---|
| 0 | `RC_OK` | Success |
| 5 | `RC_WARN` | Warning (command not understood, etc.) |
| 10 | `RC_ERROR` | Error (command failed) |
| 20 | `RC_FATAL` | Fatal error (port shutting down) |
---
## Common ARexx-Aware Applications
| Application | Port Name | Example Commands |
|---|---|---|
| Workbench | `WORKBENCH` | `OPEN`, `CLOSE`, `WINDOW` |
| MultiView | `MULTIVIEW.n` | `OPEN`, `PRINT`, `QUIT` |
| IBrowse | `IBROWSE` | `GOTOURL`, `RELOAD` |
| CygnusEd | `rexx_ced` | `OPEN`, `SAVE`, `MARK`, `CUT` |
| TextPad | `TEXTPAD` | `LOAD`, `SAVE`, `FIND` |
---
## References
- NDK39: `rexx/storage.h`, `rexx/rxslib.h`
- ADCD 2.1: rexxsyslib.library autodocs
- See also: [process_management.md](../07_dos/process_management.md) — process/task message ports

View file

@ -4,83 +4,200 @@
## Overview
`utility.library` (OS 2.0+) provides the universal tag-based parameter passing system, callback hooks, and date/time utilities used throughout AmigaOS.
`utility.library` (OS 2.0+) provides the universal tag-based parameter passing system, callback hooks, and date/time utilities used throughout AmigaOS. Nearly every OS 2.0+ API uses TagItem lists for extensible parameter passing — understanding this library is essential.
---
## TagItem System
### Structure
```c
/* utility/tagitem.h — NDK39 */
struct TagItem {
ULONG ti_Tag; /* tag identifier */
ULONG ti_Data; /* tag value */
ULONG ti_Data; /* tag value (ULONG — often cast from pointer) */
};
```
/* Special tag values: */
#define TAG_DONE 0 /* end of tag list */
#define TAG_END TAG_DONE
#define TAG_IGNORE 1 /* skip this tag */
#define TAG_MORE 2 /* ti_Data = pointer to another TagItem array */
#define TAG_SKIP 3 /* skip next ti_Data tags */
#define TAG_USER (1<<31) /* user-defined tags start here */
### Special Tags
| Tag | Value | Purpose |
|---|---|---|
| `TAG_DONE` | 0 | Terminates a tag list — must be the last entry |
| `TAG_IGNORE` | 1 | Skip this tag (placeholder) |
| `TAG_MORE` | 2 | `ti_Data` = pointer to another TagItem array (chain lists) |
| `TAG_SKIP` | 3 | Skip next `ti_Data` tags in the list |
| `TAG_USER` | `1<<31` | User-defined tags start at this value |
### How Tag Lists Work
```mermaid
flowchart LR
subgraph "Tag List (array)"
T1["SA_Width<br/>640"] --> T2["SA_Height<br/>480"]
T2 --> T3["SA_Depth<br/>8"]
T3 --> TM["TAG_MORE<br/>→ extraTags"]
end
subgraph "Chained List"
E1["SA_Title<br/>'My Screen'"] --> E2["TAG_DONE<br/>0"]
end
TM --> E1
style TM fill:#fff9c4,stroke:#f9a825,color:#333
style E2 fill:#ffcdd2,stroke:#c62828,color:#333
```
```c
/* Typical usage: */
struct Screen *scr = OpenScreenTags(NULL,
SA_Width, 640,
SA_Height, 480,
SA_Depth, 8,
SA_Title, (ULONG)"My Screen",
SA_ShowTitle, TRUE,
TAG_DONE);
/* Tag list as array: */
struct TagItem tags[] = {
{ SA_Width, 640 },
{ SA_Height, 480 },
{ SA_Depth, 8 },
{ TAG_DONE, 0 }
};
struct Screen *scr = OpenScreenTagList(NULL, tags);
```
### Tag Utility Functions
| Function | Description |
|---|---|
| `FindTagItem(tag, tagList)` | Find first matching tag |
| `GetTagData(tag, default, tagList)` | Get tag value with default |
| `NextTagItem(&tagListPtr)` | Iterate through tags |
| `TagInArray(tag, array)` | Check if tag is in an array |
| `FilterTagItems(tagList, filter, logic)` | Filter tag list |
| `CloneTagItems(tagList)` | Allocate copy of tag list |
| `FreeTagItems(tagList)` | Free cloned tag list |
| `MapTags(tagList, mapList, flags)` | Remap tag IDs |
| `FindTagItem(tag, tagList)` | Find first matching tag; returns `TagItem *` or NULL |
| `GetTagData(tag, default, tagList)` | Get tag value, or default if tag not found |
| `NextTagItem(&tagListPtr)` | Iterator — handles TAG_MORE, TAG_SKIP, TAG_IGNORE transparently |
| `TagInArray(tag, array)` | Check if a tag ID is in a ULONG array |
| `FilterTagItems(tagList, filter, logic)` | Remove/keep tags matching a filter array |
| `CloneTagItems(tagList)` | Allocate a copy of the entire tag list |
| `FreeTagItems(tagList)` | Free a cloned tag list |
| `MapTags(tagList, mapList, flags)` | Remap tag IDs (for converting between APIs) |
| `PackBoolTags(initialFlags, tagList, boolMap)` | Convert boolean tags to a flags word |
### Iterating a Tag List
```c
/* The correct way to iterate — handles all special tags: */
struct TagItem *tag;
struct TagItem *tstate = tagList;
while ((tag = NextTagItem(&tstate)))
{
switch (tag->ti_Tag)
{
case MY_WIDTH: width = tag->ti_Data; break;
case MY_HEIGHT: height = tag->ti_Data; break;
case MY_TITLE: title = (char *)tag->ti_Data; break;
}
}
```
> [!IMPORTANT]
> **Never iterate tag lists manually** with `for` loops. Always use `NextTagItem()` — it correctly handles `TAG_MORE` chains, `TAG_SKIP`, and `TAG_IGNORE`. Manual iteration will break on chained or filtered lists.
---
## Hook System
Hooks provide a standardised callback mechanism used throughout AmigaOS — Intuition, BOOPSI, layers.library, and locale.library all use them.
### Structure
```c
/* utility/hooks.h */
struct Hook {
struct MinNode h_MinNode;
struct MinNode h_MinNode; /* for linking into lists */
ULONG (*h_Entry)(void); /* assembler entry point */
ULONG (*h_SubEntry)(void); /* C function pointer */
APTR h_Data; /* user data */
};
```
Convention: `h_Entry` receives `A0=hook, A2=object, A1=message`.
### Register Convention
When a hook is called, registers are set up as:
- **A0** = pointer to the `Hook` itself
- **A2** = the "object" (context-dependent)
- **A1** = the "message" (context-dependent)
```c
/* Convenience macro for SAS/C and GCC: */
ULONG myHookFunc(struct Hook *hook __asm("a0"),
Object *obj __asm("a2"),
APTR msg __asm("a1"))
/* SAS/C / GCC with register args: */
ULONG __saveds __asm MyHookFunc(
register __a0 struct Hook *hook,
register __a2 APTR object,
register __a1 APTR message)
{
/* ... */
struct MyData *data = (struct MyData *)hook->h_Data;
/* ... process callback ... */
return 0;
}
struct Hook myHook = { {NULL}, (HOOKFUNC)myHookFunc, NULL, myData };
/* Initialise the hook: */
struct Hook myHook;
myHook.h_Entry = (HOOKFUNC)HookEntry; /* utility.library glue */
myHook.h_SubEntry = (HOOKFUNC)MyHookFunc;
myHook.h_Data = myPrivateData;
```
### Common Hook Uses
| API | Object (A2) | Message (A1) |
|---|---|---|
| BOOPSI `OM_SET` | BOOPSI object | `opSet` message |
| `InstallLayerHook` | Layer | `struct BackFillMessage` |
| Locale `FormatString` | — | Format data |
| `DoMethod` (MUI) | MUI object | Method message |
---
## Date Utilities
```c
#include <utility/date.h>
struct ClockData {
UWORD sec; /* 059 */
UWORD min; /* 059 */
UWORD hour; /* 023 */
UWORD mday; /* 131 */
UWORD month; /* 112 */
UWORD year; /* 1978+ */
UWORD wday; /* 0=Sunday */
};
/* Convert Amiga timestamp to date components: */
struct ClockData cd;
Amiga2Date(seconds, &cd); /* seconds since 1.1.1978 → date */
ULONG secs = Date2Amiga(&cd); /* date → seconds */
ULONG secs = CheckDate(&cd); /* validate and convert */
Amiga2Date(seconds, &cd);
Printf("%02d/%02d/%04d %02d:%02d:%02d\n",
cd.mday, cd.month, cd.year,
cd.hour, cd.min, cd.sec);
/* Convert date to Amiga timestamp: */
cd.year = 2024; cd.month = 3; cd.mday = 15;
cd.hour = 14; cd.min = 30; cd.sec = 0;
ULONG secs = Date2Amiga(&cd);
/* Validate a date and get timestamp: */
ULONG secs = CheckDate(&cd); /* returns 0 if invalid */
```
> [!NOTE]
> The Amiga epoch is **January 1, 1978 00:00:00 UTC**. To convert to/from Unix timestamps (epoch 1970-01-01), add/subtract **252,460,800** seconds (8 years + 2 leap days).
---
## References
- NDK39: `utility/tagitem.h`, `utility/hooks.h`, `utility/date.h`
- ADCD 2.1: utility.library autodocs
- See also: [boopsi.md](../09_intuition/boopsi.md) — BOOPSI uses hooks extensively

View file

@ -4,67 +4,185 @@
## Overview
`workbench.library` provides APIs for interacting with the Workbench desktop: AppWindows, AppIcons, AppMenuItems, and startup message handling.
`workbench.library` provides APIs for interacting with the Workbench desktop environment: receiving startup arguments, registering **AppWindows** (drag-and-drop targets), **AppIcons** (desktop icons), and **AppMenuItems** (Workbench Tools menu entries).
```mermaid
flowchart TD
subgraph "Workbench"
WB["Workbench Desktop"]
WB --> AW["AppWindow<br/>(file drop target)"]
WB --> AI["AppIcon<br/>(custom desktop icon)"]
WB --> AM["AppMenuItem<br/>(Tools menu entry)"]
end
subgraph "Application"
PORT["MsgPort"] --> LOOP["Event Loop"]
LOOP --> HANDLE["Handle AppMessage"]
end
AW -->|"User drops file"| PORT
AI -->|"User clicks icon"| PORT
AM -->|"User selects menu"| PORT
style AW fill:#c8e6c9,stroke:#2e7d32,color:#333
style AI fill:#fff9c4,stroke:#f9a825,color:#333
style AM fill:#e8f4fd,stroke:#2196f3,color:#333
```
---
## WBStartup Message
When launched from Workbench, a program receives a `WBStartup` message instead of CLI arguments:
When launched from Workbench (double-click an icon), the program receives a `WBStartup` message instead of CLI arguments:
```c
struct WBStartup {
struct Message sm_Message;
struct MsgPort *sm_Process; /* process that sent us */
BPTR sm_Segment; /* our loaded segment */
LONG sm_NumArgs; /* number of arguments */
char *sm_ToolWindow; /* tool window spec */
struct WBArg *sm_ArgList; /* argument array */
BPTR sm_Segment; /* our loaded segment list */
LONG sm_NumArgs; /* number of arguments (1 = just the tool) */
char *sm_ToolWindow; /* tool window spec (CON: string) */
struct WBArg *sm_ArgList; /* array of arguments */
};
struct WBArg {
BPTR wa_Lock; /* directory lock */
BYTE *wa_Name; /* filename */
BPTR wa_Lock; /* directory lock (BPTR) */
BYTE *wa_Name; /* filename (relative to wa_Lock) */
};
```
### Handling WBStartup
```c
/* In main(): */
struct WBStartup *wbmsg = NULL;
if (!argc) { /* launched from Workbench */
WaitPort(&((struct Process *)FindTask(NULL))->pr_MsgPort);
wbmsg = (struct WBStartup *)GetMsg(
&((struct Process *)FindTask(NULL))->pr_MsgPort);
/* Process wbmsg->sm_ArgList for file arguments */
if (argc == 0)
{
/* Launched from Workbench — get the startup message: */
struct Process *me = (struct Process *)FindTask(NULL);
WaitPort(&me->pr_MsgPort);
wbmsg = (struct WBStartup *)GetMsg(&me->pr_MsgPort);
/* ArgList[0] = the tool itself (our icon) */
/* ArgList[1..n] = files the user shift-clicked or dropped on us */
/* CD to the tool's directory for file access: */
BPTR oldDir = CurrentDir(wbmsg->sm_ArgList[0].wa_Lock);
/* Process dropped files: */
for (int i = 1; i < wbmsg->sm_NumArgs; i++)
{
BPTR dir = wbmsg->sm_ArgList[i].wa_Lock;
char *name = wbmsg->sm_ArgList[i].wa_Name;
BPTR old = CurrentDir(dir);
/* ... open and process file 'name' ... */
CurrentDir(old);
}
/* Read our ToolTypes: */
struct DiskObject *dobj = GetDiskObject(wbmsg->sm_ArgList[0].wa_Name);
if (dobj)
{
char *pubscreen = FindToolType(dobj->do_ToolTypes, "PUBSCREEN");
FreeDiskObject(dobj);
}
CurrentDir(oldDir);
}
/* ... do work ... */
if (wbmsg) {
/* ... main program logic ... */
/* MUST reply before exiting: */
if (wbmsg)
{
Forbid();
ReplyMsg((struct Message *)wbmsg);
}
```
> [!CAUTION]
> You **must** reply the WBStartup message before exiting, and you **must** call `Forbid()` before `ReplyMsg()`. If you reply without Forbid, Workbench may unload your code segment before your process finishes exiting → crash.
---
## AppWindow / AppIcon / AppMenuItem
## AppWindow — File Drop Target
Register a window to receive files when the user drags icons to it:
```c
/* Register a window to receive file drops: */
struct AppWindow *appwin = AddAppWindow(1, 0, win, port, NULL);
/* When user drags files to the window, receive AppMessage on port */
struct MsgPort *appPort = CreateMsgPort();
struct AppMessage {
struct Message am_Message;
UWORD am_Type; /* AMTYPE_APPWINDOW, etc. */
ULONG am_UserData;
ULONG am_ID;
LONG am_NumArgs; /* number of files dropped */
struct WBArg *am_ArgList; /* array of file references */
/* ... */
};
/* Register the window: */
struct AppWindow *appWin = AddAppWindow(
1, /* ID (your choice — returned in AppMessage) */
0, /* user data */
window, /* the Intuition Window */
appPort, /* port for AppMessages */
NULL); /* tags (NULL = defaults) */
RemoveAppWindow(appwin);
/* In event loop: */
struct AppMessage *amsg;
while ((amsg = (struct AppMessage *)GetMsg(appPort)))
{
for (int i = 0; i < amsg->am_NumArgs; i++)
{
BPTR old = CurrentDir(amsg->am_ArgList[i].wa_Lock);
Printf("Dropped: %s\n", amsg->am_ArgList[i].wa_Name);
/* ... open and process file ... */
CurrentDir(old);
}
ReplyMsg((struct Message *)amsg);
}
/* Cleanup: */
RemoveAppWindow(appWin);
DeleteMsgPort(appPort);
```
---
## AppIcon — Desktop Icon
Place a custom icon on the Workbench desktop:
```c
struct DiskObject *icon = GetDiskObject("PROGDIR:myapp");
struct AppIcon *appIcon = AddAppIcon(
1, /* ID */
0, /* user data */
"My App", /* label under the icon */
appPort, /* port for messages */
NULL, /* lock (NULL = Workbench root) */
icon, /* the icon imagery */
NULL); /* tags */
/* When user double-clicks or drops files on the AppIcon: */
/* → AppMessage received on appPort (same as AppWindow) */
RemoveAppIcon(appIcon);
FreeDiskObject(icon);
```
---
## AppMenuItem — Tools Menu Entry
Add an entry to the Workbench "Tools" menu:
```c
struct AppMenuItem *appMenu = AddAppMenuItem(
1, /* ID */
0, /* user data */
"My Tool", /* menu text */
appPort, /* port for messages */
NULL); /* tags */
/* When user selects the menu item: */
/* → AppMessage received, am_NumArgs=0 (no file args) */
RemoveAppMenuItem(appMenu);
```
---
@ -72,3 +190,5 @@ RemoveAppWindow(appwin);
## References
- NDK39: `workbench/workbench.h`, `workbench/startup.h`
- ADCD 2.1: workbench.library autodocs
- See also: [icon.md](icon.md) — icon.library for reading .info files and ToolTypes