forked from micropython/micropython
-
Notifications
You must be signed in to change notification settings - Fork 0
Phase 35 icon plan
Simon Dick edited this page Jun 2, 2026
·
2 revisions
Companion to the Phase 35 design block in Amiga port design. That section answers what and why; this file is the step-by-step ship plan — how to chunk the work into landable PRs.
Phase 35 graduates icon.library access from the single
read-only amiga.tooltype() peek to a full DiskObject round-trip
surface, so Python can read, edit, create, and write .info files:
from amiga import icon
dobj = icon.read("Work:Tools/Editor")
print(dobj.type, dobj.default_tool, dobj.stack_size)
dobj.stack_size = 16384
dobj.tooltypes["FONT"] = "topaz.font/8"
icon.write("Work:Tools/Editor", dobj)
new = icon.new(icon.WBPROJECT, default_tool="C:Ed",
tooltypes={"WINDOW": "CON:0/0/640/256/Title"})
icon.write("Work:Notes", new)Step 1: icon.read + DiskObject (read-only) + WB* constants
↓
Step 2: write + new + mutation
↓
Step 3: docs flip + smoke tests
| # | Step | Output | On-target smoke |
|---|---|---|---|
| 1 |
ports/amiga/modicon.c registering _icon. icon.read(path) returns a DiskObject Python type. Read-only attrs .type, .default_tool, .stack_size, .current_x, .current_y, and a read-only .tooltypes mapping (FindToolType-backed). WB* constants on the module. Wired through Makefile + frozen amiga.py. |
New C module + facade entry. | Under vamos: import works, alias amiga.icon is _icon, WB* constants are ints, missing path → OSError. Under Amiberry: icon.read("PROGDIR:micropython") returns a DiskObject whose .type == "tool" and whose tooltypes round-trip the SCRIPT= / HEAP= entries the runtime already consumes. |
| 2 | Mutation. Settable .default_tool / .stack_size / .current_x / .current_y; mutable .tooltypes mapping with [k] = v, del [k], in, iteration. icon.write(path, dobj) via PutDiskObject. icon.new(type, **kwargs) via GetDefDiskObject. |
Mutation surface complete. | Round-trip on RAM:test: create a fresh WBPROJECT icon, set default_tool + tooltypes, write, re-read, verify field equality. |
| 3 | Docs flip + comprehensive tests. |
docs/amiga.md Phase 35 → ✅; docs/amiga-testing.md gains an icon subsection; tests/ports/amiga/test_icon_smoke.py covers the surface. |
make -C ports/amiga test (or the vamos-based runner) is green. |
Each step is small: Step 1 is ~250 LOC C plus ~5 LOC Python, Step 2 adds another ~200 LOC C and ~30 LOC Python, Step 3 is paperwork.
-
ports/amiga/modicon.c(~250 LOC). Module registered as_iconviaMP_REGISTER_MODULE(MP_QSTR__icon, ...)(matching the_intuition/_aslconvention). - Module globals:
-
read(path)→DiskObject. CallsGetDiskObject(path); raisesOSError(MP_ENOENT)on NULL. -
WBDISK,WBDRAWER,WBTOOL,WBPROJECT,WBGARBAGE,WBDEVICE,WBKICK,WBAPPICON— small-int constants from<workbench/workbench.h>. -
DiskObjecttype symbol re-exported on the module (soisinstance(d, icon.DiskObject)works).
-
-
DiskObjectMicroPython type:- Holds
struct DiskObject *doplus anownedflag (always True in Step 1; flipped off if the caller wants to wrap a non-ownedDiskObject *in a future step). - Read-only attrs surfaced via
MP_TYPE_FLAG_NONE+attrslot:-
.type→ str ("disk"/"drawer"/"tool"/"project"/"garbage"/"device"/"kick"/"appicon"; falls back to the raw int for unknown types). -
.default_tool→ str or None (None whendo_DefaultToolis NULL or empty). -
.stack_size→ int (do_StackSize). -
.current_x,.current_y→ int (do_CurrentX,do_CurrentY). -
.tooltypes→DiskObjectTooltypesmapping (read-only this step, mutable in Step 2).
-
- Methods:
.close()→FreeDiskObject(do)once.__del__forwards to.close().
- Holds
-
DiskObjectTooltypesMicroPython type:- Wraps the parent DiskObject's
do_ToolTypespointer. - Step 1 surface:
__getitem__,__contains__,__iter__,__len__,keys(),values(),items(),get(k, default=None). - Step 2 adds
__setitem__/__delitem__. - Returns/iterates bytes values (matching ARexx, where keeping
AmigaOS encoding intact matters); keys are decoded as ASCII
strsince tooltype keys are conventionally upper-case ASCII. - Same encoding convention as
amiga.tooltype(): a key with a=sign returns the bit after the=; a flag-style key (no=) returns the emptyb"".
- Wraps the parent DiskObject's
-
ports/amiga/Makefile:SRC_C += modicon.cSRC_QSTR += modicon.c
-
ports/amiga/modules/amiga.py:import _icon as icon # re-exported as amiga.icon
- Reuse the existing
amiga_icon_open()helper frommain.crather than redefiningIconBase. The header for that is already pulled intomodamiga.c; declareextern bool amiga_icon_open(void);at the top ofmodicon.c. - The
attrslot for the DiskObject pattern followsmodssl.c'sssl_context_attr: dispatch on the requested attribute qstr, populatedest[0]for reads. Method/propertydest[1] != NULLfall-through is left to the standardmp_obj_generic_attrpath by callingmp_obj_generic_load_attr_or_method. - Map
do_Typeto qstrs at conversion time via a small switch so the strings live in the qstr pool, not heap. - Tooltype iteration: walk the NULL-terminated
STRPTR[]. Each entry is conceptually"KEY=VALUE"or just"KEY". For key extraction, split at the first=.
Vamos smoke (tests/ports/amiga/test_icon_smoke.py):
import _icon
import amiga
assert _icon is amiga.icon
for name in ("WBDISK", "WBDRAWER", "WBTOOL", "WBPROJECT",
"WBGARBAGE", "WBDEVICE", "WBKICK", "WBAPPICON"):
assert isinstance(getattr(_icon, name), int)
# A path that definitely doesn't exist → OSError, not crash.
try:
_icon.read("RAM:does/not/exist")
except OSError:
pass
else:
raise AssertionError("expected OSError")
print("OK")Amiberry interactive: _icon.read("PROGDIR:micropython") returns a
DiskObject. dobj.type == "tool". dobj.tooltypes["SCRIPT"]
matches whatever the tooltype currently holds.
- Mutable attributes on
DiskObject:-
.default_tool = "C:Ed"/= None— writes a copy throughAllocVec, frees the old buffer (if Python-owned). .stack_size = 16384-
.current_x = 16,.current_y = 24
-
-
DiskObjectTooltypesextended:-
__setitem__("FONT", "topaz.font/8")— value may bestr/bytes/None(None ⇒ flag-style, no=). Reallocates the underlyingSTRPTR[]if the key is new. -
__delitem__("FONT")— shrinks theSTRPTR[]array. - Frees freshly-allocated buffers on object teardown.
-
-
_icon.write(path, dobj)→ None. CallsPutDiskObject(path, do); raisesOSError(MP_EIO)on failure. -
_icon.new(type, **kwargs)→ freshDiskObject. Implementation:-
GetDefDiskObject(type)— supplies the default icon image fromENV:sys/def_*.info. - Apply kwargs (
default_tool,stack_size,current_x,current_y,tooltypesdict). - Return as Python-owned (so
FreeDiskObjectruns on teardown).
-
-
do_ToolTypesallocation policy: the original array fromGetDiskObjectis owned byicon.library; after the first mutation we copy it into anAllocVec-owned array we manage ourselves. Track ownership with a per-DiskObject flag so teardown knows whether to callFreeVecondo_ToolTypesand its strings. - For
do_DefaultTool: same pattern. The original isicon.library-owned; assignment switches to our ownAllocVecbuffer and flips an owns-default-tool flag. -
PutDiskObjectcares aboutdo_Typebeing valid and the image data being present;GetDefDiskObjectgets us a sane image. For callers who want to build a DiskObject without an image, document that the result is unwritable; we won't synthesize a placeholder.
new = icon.new(icon.WBPROJECT, default_tool="C:Ed",
tooltypes={"FOO": "bar", "FLAG": None})
icon.write("RAM:test", new)
back = icon.read("RAM:test")
assert back.default_tool == "C:Ed"
assert back.tooltypes["FOO"] == b"bar"
assert back.tooltypes["FLAG"] == b""
new.close(); back.close()-
docs/amiga.mdPhase 35 status → ✅; section gains the full surface matrix (which calls / which type / what they return). -
docs/amiga-testing.mdshorticonsubsection coveringicon.read,icon.write,icon.new, and the tooltype mapping. -
tests/ports/amiga/test_icon_smoke.pyexpanded:- Module + alias + constants.
- Missing-path OSError.
- Round-trip against
RAM:(icon.library is available under vamos — it just operates on themp:↔RAM:mapping). - Tooltype set/get/del round-trip on a fresh
new()DiskObject.
-
icon.libraryversion. We use the same v33 (OS 2.0+) baseline asmain.c'samiga_icon_open.GetDefDiskObjectis v36+ — if the host lacks it,new()should raiseOSError(MP_ENOSYS)rather than crash. Step 2 adds the runtime guard. -
Ownership flags. Each
DiskObjectPython object tracks whether it ownsdo_DefaultToolanddo_ToolTypes, because the defaultGetDiskObjectpath returns icon.library-owned memory that must not beFreeVec'd. Mutation transitions ownership to Python and the teardown path frees accordingly. -
Workbench refresh. Out of scope for Phase 35 —
PutDiskObjectwrites the.infofile; refreshing an open Workbench window isworkbench.library'sUpdateWorkbenchwhich we don't ship. Users who want the icon to reappear in an open window can close and reopen the drawer, or call out to ARexx withWORKBENCH UPDATE.
- Editing the icon's image data (
do_Gadget->GadgetRender/SelectRender). - App icons (
AddAppIcon) —workbench.library, noticon.library. - NewIcon / GlowIcon / OS3.5+ ColorIcon extended IFF chunks.
- Drag-and-drop / IDCMP integration.