Calm Commander: jak psát viewer pluginy
Calm Commander umí načítat externí viewer pluginy z adresáře c:/CalmCommander/plugin. Plugin není samostatná aplikace s vlastním loaderem, ale malý binární modul .ccp, který Calm Commander nahraje do paměti, předá mu kontext aktuálního souboru a po návratu zase obnoví file manager.
Tenhle text je zaměřený na psaní vlastního pluginu. Nebudeme tedy řešit obecnou myšlenku modularity, ale konkrétní ABI: kam se plugin skládá, co dostane v registrech, co je v plugin_api.i.asm, jak číst data souboru a jak vrátit řízení zpět do Calm Commanderu.
text.ccp- textový viewer s režimy text/hex/dec a hledánímzxscreen.ccp- prohlížeč klasických 6912b ZX Spectrum obrazoveknxi.ccp- viewer NXI obrázkůpt2test.ccp,pt3test.ccp,stctest.ccp,stptest.ccp,sqtest.ccp- hudební test/player pluginyHelloWord.ccp- malá referenční ukázka plugin contractu
Co musí plugin splňovat
Plugin se assemblí jako kód od adresy $C000. Calm Commander ho načítá do samostatné banky a volá jeho první instrukci právě na této adrese. Velikost pluginu je teď omezená na 4096 bajtů, tedy rozsah $C000-$CFFF.
DEVICE ZXSPECTRUMNEXT
org VIEW_PLUGIN_ADDRESS
include "plugin_api.i.asm"
plugin_start
; HL = pointer na PluginContext
; DE = pointer na service table
...
plugin_end
assert plugin_end - plugin_start <= VIEW_PLUGIN_SIZE
SAVEBIN "plugin/myviewer.ccp", VIEW_PLUGIN_ADDRESS, VIEW_PLUGIN_SIZE
Vstupní bod není pojmenovaný symbol, který by loader vyhledával. Je to jednoduše začátek binárky na $C000. Proto je dobré mít hned na začátku plugin_start a za ním inicializaci kontextu.
plugin_api.i.asm
Soubor plugin/plugin_api.i.asm je malý, ale je to hlavní veřejné rozhraní mezi Calm Commanderem a pluginem. Plugin by si neměl opisovat konstanty ručně, ale includovat právě tenhle soubor.
VIEW_PLUGIN_ADDRESS equ 49152
VIEW_DATA_ADDRESS equ 57344
VIEW_PLUGIN_SIZE equ 4096
VIEWCTX_ABI equ 0
VIEWCTX_TYPE equ 1
VIEWCTX_FILENAME equ 2
VIEWCTX_SIZE_LO equ 4
VIEWCTX_SIZE_HI equ 6
VIEWCTX_DATA_PAGE equ 8
VIEWCTX_DATA_ADDR equ 9
VIEWCTX_READ_LEN equ 11
VIEWCTX_PAGE_COUNT equ 13
VIEWCTX_DATA_PAGES equ 14
VIEWCTX_SERVICES equ 16
SERVICE_PRINT equ 0
SERVICE_INKEY equ 2
SERVICE_WINDOW equ 4
SERVICE_LAYER0 equ 6
SERVICE_INPUT_NOWAIT equ 8
Volání pluginu
Calm Commander před voláním připraví data vybraného souboru, nahraje plugin, vyplní context a zavolá adresu $C000. Při vstupu platí:
HLukazuje naPluginContextDEukazuje na tabulku služeb Calm CommanderuAobsahuje ABI verzi, aktuálně1- kód pluginu je namapovaný na
$C000 - aktuální datová stránka souboru se mapuje na
$E000
Plugin se vrací obyčejným ret. Návratová hodnota je v registru A: 0 znamená zavřít viewer a vrátit se do Calm Commanderu, 1 znamená zavřít viewer a požádat Calm Commander o posun na další soubor.
PluginContext
Context je malá struktura, kterou si plugin typicky uloží do vlastní proměnné a čte ji přes IX. Není potřeba znát vnitřnosti Calm Commanderu, stačí offsety z plugin_api.i.asm.
plugin_start
ld (ctxPtr),hl
ld (svcPtr),de
call patch_services
ld ix,(ctxPtr)
ld a,(ix+VIEWCTX_ABI)
cp 1
jr nz,.unsupported
ld e,(ix+VIEWCTX_FILENAME)
ld d,(ix+VIEWCTX_FILENAME+1)
; DE teď ukazuje na jméno souboru ukončené nulou
VIEWCTX_SIZE_LO a VIEWCTX_SIZE_HI tvoří 32bitovou velikost souboru. Menším viewerům často stačí nižších 16 bitů, ale textový viewer např. kontroluje i horních 16 bitů a při velkém souboru pracuje jen s načtenou částí.
VIEWCTX_READ_LEN říká, kolik bajtů je dostupných v prvním načteném bloku. VIEWCTX_PAGE_COUNT a VIEWCTX_DATA_PAGES popisují seznam 8K stránek, které Calm Commander načetl pro aktuální soubor.
Čtení dat souboru
Nejjednodušší plugin může číst první blok přímo z VIEW_DATA_ADDRESS. To je vhodné např. pro jednoduché formáty, které se vejdou do první stránky nebo mají hlavičku na začátku souboru.
ld hl,VIEW_DATA_ADDRESS
ld a,(hl)
; A = první bajt načteného souboru
Pro větší soubory je důležitý seznam stránek. VIEWCTX_DATA_PAGES ukazuje na pole MMU page čísel. Plugin si vybere příslušnou stránku, namapuje ji do MMU slotu pro $E000 a potom čte z VIEW_DATA_ADDRESS.
ld ix,(ctxPtr)
ld l,(ix+VIEWCTX_DATA_PAGES)
ld h,(ix+VIEWCTX_DATA_PAGES+1)
ld a,(hl) ; první datová MMU page
nextreg $57,a ; MMU7 = $E000-$FFFF
ld hl,VIEW_DATA_ADDRESS
Calm Commander dnes připravuje až osm 8K stránek. Textový viewer toho využívá pro soubory větší než prvních 8 KB, zatímco jednodušší viewery si vystačí s aktuálně namapovanou datovou adresou.
Service table
Plugin nemá volat náhodné adresy z Calm Commanderu. Dostane tabulku služeb v DE a z ní si opíše adresy rutin, které potřebuje. V ukázkových pluginech se to dělá tak, že se lokální call 0 placeholdery opatchují skutečnými adresami.
patch_services
ld ix,(svcPtr)
ld l,(ix+SERVICE_PRINT)
ld h,(ix+SERVICE_PRINT+1)
ld (call_print+1),hl
ld l,(ix+SERVICE_WINDOW)
ld h,(ix+SERVICE_WINDOW+1)
ld (call_window+1),hl
ld l,(ix+SERVICE_INPUT_NOWAIT)
ld h,(ix+SERVICE_INPUT_NOWAIT+1)
ld (call_input+1),hl
ret
call_print
call 0
ret
call_window
call 0
ret
call_input
call 0
ret
SERVICE_PRINT používá stejnou konvenci jako interní tisk Calm Commanderu: DE ukazuje na nulou ukončený text, HL nese pozici jako H = sloupec, L = řádek a A je atribut/barva. SERVICE_WINDOW kreslí okno, kde HL je pozice a BC velikost.
Pro vstup je nejlepší používat SERVICE_INPUT_NOWAIT. Je neblokující a přitom nechává běžet obsluhu myši. Viewer díky tomu nezmrazí kurzor ani zbytek vstupní logiky.
Minimální plugin
Tohle je zkrácená kostra podle plugin/HelloWord.asm. V reálném pluginu si pravděpodobně uložíte stack, vykreslíte vlastní obrazovku, počkáte na puštění spouštěcí klávesy a potom budete v input loopu reagovat na klávesy nebo myš.
DEVICE ZXSPECTRUMNEXT
org VIEW_PLUGIN_ADDRESS
include "plugin_api.i.asm"
PLUGIN_STACK equ $DFFE
plugin_start
ld (ctxPtr),hl
ld (svcPtr),de
call patch_services
ld (savedSp),sp
ld sp,PLUGIN_STACK
ld hl,0*256+5
ld bc,78*256+20
ld a,16
call call_window
ld de,titleText
ld hl,2*256+6
ld a,16
call call_print
.input_loop
halt
call call_input
or a
jr z,.input_loop
cp 1
jr z,.stop
cp 2
jr z,.next
jr .input_loop
.stop
ld sp,(savedSp)
xor a
ret
.next
ld sp,(savedSp)
ld a,1
ret
titleText defb "My viewer",0
ctxPtr defw 0
svcPtr defw 0
savedSp defw 0
; sem patří patch_services a call_* wrappery
plugin_end
assert plugin_end - plugin_start <= VIEW_PLUGIN_SIZE
SAVEBIN "plugin/myviewer.ccp", VIEW_PLUGIN_ADDRESS, VIEW_PLUGIN_SIZE
Ovládání vstupu
Hodnoty z SERVICE_INPUT_NOWAIT závisí na typu vieweru. Společné pravidlo je, že 0 znamená žádný vstup a 1 ukončení vieweru. U pluginů, které umí procházet soubory, se používá také 2 jako požadavek na další soubor. Textový viewer navíc reaguje na hodnoty pro posun po řádcích a stránkách.
U obrázkových a hudebních viewerů je dobrý vzor HelloWord.asm: po startu počkat na puštění původní klávesy, krátce "armed delay" a teprve potom přijímat ovládání. Bez toho se může stát, že plugin okamžitě zareaguje na klávesu, kterou byl spuštěn.
Přidání pluginu do menu
Samotný .ccp soubor nestačí, pokud má být plugin dostupný z výběrového menu Calm Commanderu. Loader má tabulku v functions/viewer.asm, kde jsou přiřazené typy, názvy souborů a popisky menu.
viewPluginMenuTable
defb VIEWTYPE_TEXT : defw viewTextPluginName : defw viewPluginMenuTextTxt
defb VIEWTYPE_ZXSCREEN : defw viewZxScreenPluginName : defw viewPluginMenuZxTxt
defb VIEWTYPE_NXI : defw viewNxiPluginName : defw viewPluginMenuNxiTxt
; další položky...
K novému typu tedy patří tři změny: přidat VIEWTYPE_* konstantu, přidat jméno souboru pluginu ukončené hodnotou 255 a přidat text do menu ukončený nulou. Pokud má Calm Commander plugin vybírat automaticky, je potřeba ještě doplnit rozpoznání souboru ve výběrové logice.
Sestavení
Pluginy v repozitáři se skládají přes sjasmplus. Vzor je stejný jako u existujících souborů v adresáři plugin: zdroj includuje plugin_api.i.asm, na konci má assert velikosti a SAVEBIN vytvoří výsledný .ccp.
sjasmplus plugin/myviewer.asm
Po sestavení musí být výsledný soubor v NextZXOS dostupný v c:/CalmCommander/plugin. V repozitáři se generuje do plugin/*.ccp, odkud ho lze zkopírovat do instalace Calm Commanderu.
Praktické poznámky
- Držte se limitu
VIEW_PLUGIN_SIZE. Pokud plugin přeroste 4 KB,assertmusí spadnout. - Uložte a obnovte
SP, pokud použijete vlastní stack. Ukázkové pluginy často dávají stack pod konec plugin prostoru. - Pro tisk a okna používejte service table, ne pevné adresy z hlavního programu.
- Pro čtení dat používejte context a seznam MMU stránek. Nespoléhejte na to, že celý soubor leží souvisle v paměti.
- Pokud plugin mění zobrazení, musí po sobě uklidit tolik, aby se Calm Commander mohl vrátit do svého UI. Loader po návratu obnovuje panely, ale plugin by neměl nechávat hardware v náhodném stavu.
- Jako referenci berte hlavně
HelloWord.asmpro ABI,text.asmpro práci s více stránkami dat azxscreen.asm/nxi.asmpro grafické viewery.
Shrnutí
Viewer plugin pro Calm Commander je malý modul na $C000, který dostane v HL context, v DE service table a data souboru dostupná přes $E000 a seznam MMU stránek. Základní workflow je: includovat plugin_api.i.asm, uložit context, opatchovat služby, zpracovat data, reagovat na vstup a vrátit v A výsledek.
Nejlepší start pro nový viewer je zkopírovat strukturu z HelloWord.asm, přejmenovat výstupní .ccp, doplnit vlastní čtení dat a až potom plugin přidat do tabulky v functions/viewer.asm. Tím zůstane hlavní Calm Commander malý a specializované formáty mohou žít jako samostatné rozšíření.

