In der letzen Folge ging es um die Installation des Emulators und die Problematik mit dem GEM-Vektor. Diesmal werde ich Ihnen u.a. einige der neuen bzw. erweiterten Escape-Sequenzen vorstellen.
Bevor ich mit der weiteren Besprechung des Programms fortfahre, möchte ich Ihre Aufmerksamkeit zunächst einmal auf die in Tabelle la und lb dargestellten globalen Variablen TCB und CCB lenken. Da fast alle Funktionen auf diese Strukturen zugreifen, ist es für das Verständnis des Programms nicht ganz unwichtig, zu wissen, welche Informationen sie enthalten. Im allgemeinen wird auf beide Strukturen nur über Offsets zur Basisadresse zugegriffen. Weil sich das so herzerfrischend wissenschaftlich und also chinesisch anhört, hier ein Beispiel: Zu Beginn einer Funktion wird die Adresse des Cursor-Control-Blocks CCB mit dem Befehl "lea CCB,a4” in ein Adress-Register des MC68000 geladen. Möchte man nun die absolute Cursorposition (irgendeine Adresse im Video-RAM) eruieren, so kann man mit 6(a4) darauf zugreifen. Im Prinzip ist das nichts anderes als die Assembler-Variante des von Pascal und C her bekannten Zugriffs auf Records/ Structs und Unions, also z.B. abs_curs(:)=CCB.address. Sie sehen, daß das Verständnis der Maschinensprache manchmal ganz nützlich sein kann, wenn es darum geht, sich abstrakte und damit nicht immer leicht verständliche Sprachkonstrukte zu vergegenwärtigen, wobei dies insbesondere für die leidigen Pointer gilt! ich nehme an, daß Sie dank der Kommentare nun keine Schwierigkeiten mehr haben werden, die beiden Kontrollvariablen zu verstehen. Aber noch eine weitere fundamentale Vorbetrachtung ist zum Programmverständnis notwendig, nämlich die Organisation der systemeigenen
Was nützt ein mit allen gängigen Superlativen dekorierter Computer, wenn er sich nicht in der Lage sieht, sich im Klartext mit dem Benutzer zu unterhalten? Gewieft, wie die Hersteller nun ‘mal sind, haben sie Mittel und Wege ersonnen, die uns allen bekannten Schriftzeichen zur Darstellung zu bringen. Dabei gehen allerdings sowohl Mittel wie Wege ihre eigenen ebensolchen. Hersteller X zieht es beispielsweise vor, den ganzen Zeichenkrempel der Hardware zu überlassen; entsprechende Chips hören auf den Namen Character Generators. Der Vorteil hierbei liegt sicherlich in der Einfachheit der Bedienung und der Darstellungsgeschwindigkeit. Man übergibt dem Chip schlicht die Nummer des Zeichens, das darzustellen man sich wünscht - und läßt die Transistoren pfriemeln. Die Nachteile sind aber auch nicht ganz ohne: abgesehen davon, daß selbst Chips "Made in Taiwan" nicht für umsonst zu haben sind, erlauben nur einige wenige den Gebrauch von Textattributen (hell, blinkend, kursiv...). und dann haben sie auch ihren Preis. Ergo gehen andere Hersteller den Weg über die Software. Die ist zwar auch nicht gerade billig, aber wenn sie erst einmal erstellt ist, kann man sie beliebig oft und quasi gratis ohne Qualitätsverlust vervielfältigen (was zugegebenermaßen nicht jedem gefällt - die verschiedensten Kopierschutzmechanismen sprechen da eine deutliche Sprache...). Wie funktioniert nun aber die softwaremäßige Zeichendarstellung? Die Voraussetzung hierzu ist auf jeden Fall, daß der Bildschirm zur Kategorie “bitmapped" gehört.
Weil man sich darunter wieder schrecklich viel vorstellen kann, sei’s kurz erläutert. Der SM 124 bringt am ST 640 mal 400 Pixel (Abk. für: picture elements) zur Darstellung. Jeder dieser Bildpunkte wird durch ein Bit im Hauptspeicher des Rechners repräsentiert, d.h., der Bildschirm ist nichts anderes als ein durchgehender RAM-Bereich einer durch die Auflösung bestimmten Länge. Beim Atari sind dies 640*400=256000 Bits durch 8 macht 32000 Bytes. Möchte man nun einen Punkt setzen, wird im mit diesem Punkt korrespondierenden Byte einfach das entsprechende Bit auf “1” gesetzt (raten Sie mal, wie das Löschen funktioniert!). Ein besonderer Chip ist darauf spezialisiert, das Video-RAM ständig abzutasten und die entsprechenden Signale zu erzeugen, die der Bildschirm dann letztendlich darstellen kann. So funktioniert’s bei monochromatischer Darstellung; kommt Farbe ins Spiel, wird’s schon kniffliger. Will man beispielsweise vier Farben darstellen, so benötigt jeder Bildpunkt schon 2 Bit an Information. Die entsprechende Bitkombination muß dann ebenfalls von der Hardware interpretiert werden. Soll der Bildspeicher weiterhin seine Größe von 32000 Bytes behalten, läßt es sich nicht vermeiden, daß sich die Anzahl der darstellbaren Pixel halbiert! Das ist auch der Grund, warum man in der mittleren Auflösung zwar vier Farben, aber nur noch 640 mal 200 Bildpunkte darstellen kann. Übrigens nennt man die Gesamtheit eines Bits über alle Pixel eine “bitplane” (bei Monochrombetrieb gibt’s nur eine, bei vier Farben zwei, bei sechzehn Farben vier, bei... na, Sie wissen schon: Logarithmus dualis und so).
Nach diesem Ausflug in die Welt der “bitmapped devices” können Sie sich sicher leicht vorstellen, wie man nun Zeichen auf den Monitor bringt. Jedes Zeichen wird durch eine Kombination aus gesetzten oder eben nicht gesetzten Bits repräsentiert, und zwar in einem Feld von soundsoviel mal soundsoviel Punkten. Im einfacheren Fall ist die Größe des Rasters für alle darstellbaren Zeichen konstant. Im Atari gibt’s gleich drei System-Zeichensätze (Fonts), bei denen dies so gelöst ist (6x6,8x8 und 8x16). Daß es aber auch anders geht, wissen Kenner von Apple’s Mac zu berichten (hat da jemand Aladin gesagt?). Bei diesen Rechnern hat man nicht nur beliebig viele Zeichensätze zur Verfügung, sie sind auch alle für Proportionalschrift ausgelegt, d.h. ein ‘i’ benötigt weniger Platz als ein ‘M’. Im Endeffekt sieht das dann zwar ein wenig ästhetischer aus, ist aber wesentlich aufwendiger. Stellen Sie sich vor, Sie müssen ein Zeichen löschen: abgesehen davon, daß es nicht ganz einfach ist, den Cursor genau auf das Zeichen zu setzen - wieviele Pixel sind’s denn nun, die auszuradieren sind? Ich nehme an, daß Franze “SIGNUM!” Schmerbeck (Tagchen auch) hier ein paar Klagelieder anzustimmen weiß... Bleiben wir also lieber bei der Darstellung äquidistanter Zeichen.
Problem: wie legt man die Font-Daten im ROM ab? Exemplarisch sei’s am 8xl6-Font erklärt (bei den anderen Fonts ist es genauso). Man zerlegt hierzu die Zeichen in sogenannte “scanlines”, wovon es logischerweise 16 gibt. Ab der Startadresse der Fontdaten stehen dann hintereinander: die oberste Zeile von chr(0), gefolgt von der obersten Zeile von chr(1), gefolgt... und so geht das denn bis 255. Dann kommt die zweitoberste Zeile von chr(0) und so weiter. Glücklicherweise ist nun eine Scan-line genau acht Bit breit, so daß man sehr simpel auf ein Zeichen zugreifen kann: die oberste Zeile findet man an der Adresse FontStartAdresse + ASCII-Nummer(Zeichen). Die zweite befindet sich 256 Bytes weiter, die dritte... Nun weiß man aber von GEM/VDI-Programmen, daß auch ein ST zu Textattributen bis hin zur Proportionalschrift befähigt ist. Zu diesem Behufe ist wie üblich wieder Verwaltung notwendig, welche im “Fontheader” residiert. Dort findet man z.B., welche Zeichen überhaupt im Font gespeichert sind, wo die Startadresse des Fonts liegt, wie er heißt und einen Zeiger auf die sogenannte “Offset-Tabelle”.
In dieser ist nun gespeichert, wieviele Bits jedes Zeichen breit ist. Somit müssen nur die Scanlines des ersten darstellbaren Zeichens auf einer Wortgrenze (gerade Adresse) beginnen; die restlichen Daten können über die Offset-Tabelle gefunden werden, was jedoch recht zeitaufwendig und rechenintensiv ist. Für den VT52-Emulator wie auch für die VDI-Escapes ist das aber alles vollkommen wurscht: sie erwarten, daß jedes Zeichen exakt ein Byte breit ist und damit basta! Ich habe die Existenz der Offset-Tabelle lediglich der Vollständigkeit halber erwähnt; sie ist, wie gesagt, für den Teil der Textausgabe, um den es hier geht, nicht relevant. Was ich Ihnen nun lang und breit erklärt habe, ist die in Prosa gefaßte Prozedur PUT, die Sie am Anfang des Listings wiederfinden können. Abgesehen davon, daß sie ein Zeichen auf den Bildschirm bringt, sorgt sie auch dafür, daß - je nach eingestellten Attributen - das Zeichen unterstrichen, halbhell (disabled) oder invertiert dargestellt wird, wobei diese Attribute auch kombiniert werden können. Wie man diese Attribute wählen kann, erfahren Sie im folgenden Kapitel.
xVT52 erweitert die Anzahl der Steuerzeichen auf mehr als das Doppelte. Das bedeutet zwar, daß Programme, die von den neuen Möglichkeiten Gebrauch machen, nicht mehr ohne den erweiterten Emulator lauffähig sind (besser gesagt: sie laufen zwar auch unter dem normalen VT52-Emulator, weil dieser - Ausnahmen ausgenommen - die neuen ESC-Sequenzen einfach ignoriert), andererseits dürfte es wohl keine Schwierigkeiten bereiten, xVT52.PRG mit seinen 4.5 Kilobytes Speicherbedarf mit auf die entsprechende Programmdiskette zu kopieren, oder?
An den folgenden Escapes hat sich nichts geändert (d.h. sie unterscheiden sich nicht von denen des Originals):
Im übrigen sind auch die normalen Steuerzeichen Carriage Return, Line Feed, VT, Backspace und Bell (Wau!) gleich geblieben.
Folgende Steuerzeichen weichen von der VT52-Norm ab:
Von den gewöhnlichen Steuerzeichen hat sich TAB (0x9) geändert. Während die Tabulatorweite beim VT52 konstant 8 Zeichen beträgt, gestattet xVT52 das Setzen von bis zu 80 (!) Tabulatoren an beliebigen Spalten (zugegeben: bei Ausreizen der 80 Tabs ist die Beliebigkeit zumindest beim 80. natürlich etwas eingeschränkt!!). Standardmäßig ist jede achte Spalte mit einem Tabulator versehen, so daß man auch hier ohne weiteres zur Norm kompatibel bleiben kann, sofern man’s möchte. Für den einfachen Aufbau von Tabellen ist es jedoch sicherlich nützlich, von den erweiterten Möglichkeiten Gebrauch zu machen. Hierzu stehen vier Escapes bereit:
Beginnen wir der Einfachheit halber wieder mit dem Ende: Ein Emulator ist die Simulation von Hardware mittels Software, d.h. ein Programm tut so, als ob, ist es aber gar nicht! So ist es ist beispielsweise möglich, den guten alten ST per Programm in einen noch guteren und älteren Macintosh odergar in den allergutesten und alleraltesten Ih Bäh Mhh zu verwandeln, ohne auch nur ein Schräubchen berühren zu müssen. “Fein!”, werden Sie jetzt sagen, “Und was hat das alles mit meinem VT52 zu tun?”. Es hat. Sie müssen sich vorstellen, daß es Zeiten gab, in denen Computer sehr, sehr teuer waren. Anstatt also jedem Mitarbeiter einen Mega ST4 mit 60MB-Festplatte und Laserdrucker auf den Tisch zu stellen, gab es nur einen biederen grün leuchtenden Monitor, während sich der sogenannte Mainframe irgendwo zwischen Waschküche und Heizungskeller befand (zum einen, weil er so laut war, und zum anderen, weil die Warmluft der Röhren sich vortrefflich zum Wäschetrocknen eignete). Jaja, damals gab’s halt noch keine Stiftung Staren-Nest und der Begriff “71 Hertz” wurde noch vergeblich in Biologiebüchern gesucht. Zu dieser Zeit also trug es sich zu, daß man vor dem Problem stand, daß der Benutzer irgendwie mit dem Rechner in Verbindung treten mußte (darf ich’s kommunizieren nennen?). Also baute man eine Hardware, nannte sie Terminal und befahl ihr, Dolmetscher zu spielen. In ihrem Innersten gab es eine Schaltung (heute würde man ja “Chip” sagen, aber damals gab es nur solche aus Kartoffeln), also etwas ureigen Elektrisches, was mittels eines Protokolls den Datenaustausch zwischen Tastatur, Mainframe und Monitor bewerkstelligte (Mäuse gab’s ja nur im Heizungskeller...). Natürlich ist die Problematik bis heute die gleiche geblieben; es gibt nach wie vor Zentralrechner, die eine bestimmte Anzahl von Terminals bedienen können. Nur werden die Protokolle nicht mehr nur über die Hardware realisiert - zunehmend übernehmen auch Programme diese Aufgaben. Womit wir den Kreis geschlossen hätten, denn nun sind wir wieder beim Emulator. Bleibt immer noch die Frage nach dem ominösen Offset von 32. Er hängt halt zusammen mit dem erwähnten Protokoll (keine Angst, das ist nichts Unanständiges und gibt auch keine Flensburger!). Es regelt nämlich solch enorm wichtige Dinge wie etwa die Übertragungsgeschwindigkeit und Datensicherheit (Stichwort: Checksummenbildung), unterstützt aber auch besondere Funktionen, die mit speziellen Steuerzeichen ausgelöst werden (wie z.B. mit ESC, 0x1 B). Damit nun aber unterschieden werden kann, was simpler Text ist und was nicht, hat man den Steuerzeichen einfach spezielle Codes zugeordnet. Beim ASCII-Code zum Bleistift liegen die Steuerzeichen im Bereich bis OxlF, Zahlen, Buchstaben und Sonderzeichen zwischen 0x20 und 0x7F. Das genau ist der Grund, warum der Offset unbedingt notwendig ist - ohne ihn würde man sonst ungewollt ein Steuerzeichen abschicken, was schlimmstenfalls einen Abbruch der Datenübertragung zur Folge hätte! Das hätte ich Ihnen zwar alles viel kürzer verklik-kern können, aber warum sollte ich mich bremsen, wenn’s gerade gut aus der Feder läuft? Ich hoffe. Sie teilen meine Ansicht, nichts sei langweiliger als geballtes Wissen im DIN-Format; und vom Schmunzeln ist ja wohl auch noch keine Muskelfaser gerissen...
Zum Thema Textattribute stehen unter xVT52 folgende ESCapes zu Ihren Diensten:
Halbhelle Darstellung ist dabei gleichbedeutend mit dem Attribut “disabled” bei Drop-Down-Menüs und Dialogboxen. Die für diesen Effekt verantwortliche Funktion heißt LIGHTMASK und löscht für jede Scanline abwechselnd alle geraden und ungeraden Pixel, indem sie den ursprünglichen Inhalt mit 0x55 (%01010101) bzw. 0xAA (%10101010) verUNDet. Das Unterstreichen dagegen ist noch in der Routine PUT untergebracht, weil hier ja lediglich die unterste Scanline des Zeichens mit OxFF zu ersetzen ist -sehr ökonomisch. Zwei weitere neue Sequenzen kümmern sich um den Font:
Hierbei ist zu erwähnen, daß beim Umschalten vom 8x16 in den 8x8-Font die Zeile verdoppelt und umgekehrt halbiert wird, damit die Cursorposition (zumindest bei geraden Zeilen) unverändert bleibt. Bereits zu Beginn des ersten Teils hatte ich ja bereits erwähnt, daß man mit ESC i die Ausgabeparameter zurücksetzen kann, so daß sich eine weitere Erklärung erübrigt. Ich verweise hier nochmal auf die Funktion INIT_CONOUT, die sich in des Listings erstem Teil befindet und aus der Sie ersehen können, was alles initialisiert wird. Da fast jede Zeile des Listings dokumentiert ist (was ich Ihnen für Assemblerprogramme nur wärmstens zur Nachahmung empfehlen kann!), hoffe ich, daß Sie mit dem Programm einigermaßen zurecht kommen - auch wenn Sie nicht gerade zu den Koryphäen des Motorola-Jargons zählen sollten...
TCB:
dc.w 0 ; 0(TCB) aktuelle Spalte
dc.w 0 ; 2(TCB) aktuelle Zeile
dc.w 79 ; 4(TCB) maximale Spalte
dc.w 24 ; 6(TCB) maximale Zeile
dc.b 2 ; 8(TCB) Bitvektor für Attribute:
Bit 0=1: Invertieren ein (ESC p)
Bit 1=1: Wrapping ein (ESC v)
Bit 2=1: Unterstrich ein (ESC y)
Bit 3=1: Halbhell ein (ESC R)
dc.b 0 ; 9(TCB) Grafikflag (ESC r; 0=aus, -1=ein)
dc.w 0 ; 10(TCB) Anzahl gespeicherter Cursorpositionen (ESC j)
dc.w 0,0 ; 12(TCB) 1. gespeicherte Position (x, y)
dc.w 0,0 ; 16(TCB) 2. -"-
dc.w 0,0 ; 20(TCB) 3. -"-
dc.l 0 ; 24(TCB) Zeiger auf 8x16-Fontdaten (GEM).
dc.l 0 ; 28(TCB) Zeiger auf 8x8-Fontdaten (GEM)
dc.l 0 ; 32(TCB) Zeiger auf aktuellen Font
dc.w 16 ; 36(TCB) Höhe eines Zeichens in Pixel
dc.w 16*80 ; 38(TCB) Bytes pro Textzeile
Tabelle 1a: Aufbau der Variablen TCB (TerminalControlBlock)
Damit’s für diesmal nicht zu lang wird, möchte ich an dieser Stelle einen Breakpoint setzen; jedoch nicht ohne Ihnen beim Abtippen viel Spaß und wenig Krämpfe gewünscht und Sie darauf hingewiesen zu haben, was Sie im dritten Teil erwartet: Einerseits die weitere Besprechung der neuen ESC-Sequenzen, andererseits will ich Ihnen verraten, wie man zeitkritische Programme auf Trab bringen kann und - naja, mal sehen... Alsdann tschüß bis neulich!
MS
CCB:
dc.b 2 ; 0(CCB) Bitvektor für Cursor-Status:
Bit 0=1: Cursor enable (für Interrupt!)
Bit 1=1: Cursor darf blinken
Bit 2=1: Cursorposition invertiert
Bit 3=1: Cursor eingeschaltet
dc.b 0 ; 1(CCB) Anzahl gespeicherter CUR_OFFs (ESC f)
dc.w 20 ; 2(CCB) Blinkrate
dc.w 20 ; 4(CCB) Zähler für Blinken (für Interrupt)
dc.l 0 ; 6(CCB) absolute Cursorposition (im Video-RAM)
**Tabelle 1b: Aufbau der Variablen CCB (CursorControlBlock) **
PUTs ; Zeichen in d0.w ausgeben
; (verändert! a0-a4/d0-d3)
lea CCB.a4 ; ^Cursor-Controlblock
bclr #0,(a4) ; disable Cursor
bclr #2,(a4) ; Cursorposition nicht invertiert
lea TCB,a0 ; ^TerminalControlBlock
move.l 32(a0),a1 ; ^aktuelle Fontdaten
lea \wr_16,a3 ; (16 Scan-Zeilen ausgeben)
cmp.l 24(a0),a1 ; aktueller Font=8xl6?
beq.s \write ; ja
lea \wr_end,a3 ; sonst nur 8 Scan-Zeilen ausgeben
\write:
adda.w d0,a1 ; Offset addieren
move.l 6(a4),a2 ; abs. Cursorposition
move.b (a1),(a2)+ ; Scan-Zeilen übertragen; Spalte ++
move.b 01*256(a1).01*80-1(a2)
move.b 02*256(a1),02*80-1(a2)
move.b 03*256(a1),03*80-1(a2)
move.b 04*256(a1),04*80-1(a2)
move.b 05*256(a1),05*80-1(a2)
move.b 06*256(a1),06*80-1(a2)
move.b 07*256(a1),07*80-1(a2) ; letzte Scan-Zeile 8x8
jmp (a3) ; Je nachdem, ob 8x8 oder 8xl6-Font gewählt
\wr_16: ; ist. \wr_end oder \wr_16 anspringen
move.b 08*256(a1),08*80-1(a2)
move.b 09*256(a1),09*80-1(a2)
move.b 10*256(a1),10*80-1(a2)
move.b 11*256(a1),11*8B-1(a2)
move.b 12*256(a1),12*80-1(a2)
move.b 13*256(a1).13*80-1(a2)
move.b 14*256(a1),14*80-1(a2)
move.b 15*256(a1).15*80-1(a2) ; letzte Scan-Zeile 8x16
\wr_end:
btst #2,8(a0) ; Unterstrich ein?
beq.s \half ; nein
moveq #80,d1 ; Bytes/Pixelzeile
move.w 36(a0),d2 ; Zeichenhöhe in Pixel
subq.w #1,d2 ; -1
mulu d2,d1 ; Offset bestimmen
move.b #$FF,-1(a2,d1,w); letzte Scan-Zeile unterstreichen
\half:
btst #3,8(a8) ; halbe Helligkeit ein?
beq.s \inv ; nein
bsr LIGHTMASK ; sonst maskieren
\inv:
btst #0,8(a0) ; inverse Darstellung?
beq.s \inc_col ; nein
bsr CUR_INV ; sonst invertieren
bchg #2,(a4) ; alten Cursor-Status restaurieren
\inc_col:
moveq #80,d1 ; Bytes/Pixelzeile
move.l a2,6(a4) ; abs. Cursorposition++
addq.w #1,(a0) ; Spalte inkrementieren
cmp.w (a0),d1 ; letzte Spalte erreicht?
bgt.s \return ; nein, fertig
btst #1,8(a0) ; wrap eingeschaltet?
bne.s \wrap ; ja
subq.w #1,(a0) ; sonst letzte Spalte
subq.l #1,6(a4) ; und abs. Cursorposition beibehalten
bra.s \return ; Ende
\wrap:
clr.w (a0) ; Spalte 0
move.w 38(a0),d3 ; Bytes/Textzeile
ext.l d3 ; auf Langwort bringen
add.l d3,6(a4) ; + abs. Cursorposition
sub.l dl,6(a4) ; - 1 Pixelzeile = neue Position
addq.w #1,2(a0) ; Zeile ++
move.w 6(a0),d2 ; letzte Zeile
cmp.w 2(a0),d2 ; überschritten?
bpl.s \return ; nein, fertig
subq.w #1,2(a0) ; sonst letzte Zeile beibehalten
sub.l d3,6(a4) ; ebenso abs. Position
bsr SCROLL_UP ; alles 1 Zeile hochschieben
bsr DEL_LINE ; und letzte Zeile löschen
\return:
bset #0,(a4) ; enable Cursor
clr.l d0 ; "kein Fehler"
rts ; tschau
CUR_INV: ; Cursorposition invertieren
; (verändert! d1/a1-a2/a4)
move.l a2,-(a7) ; Register retten
lea TCB,a0 ; ^TerminalControlBlock
lea CCB,a4 ; ^CursorControlBlock
move.l 6(a4),a1 ; abs. Cursor-Adresse
lea \inv_end,a2 ; 8*8 annehmen
move.w 36(a0),d1 ; Zeichenhöhe
subq.w #8,d1 ; -Offset für 8*8
beq.s \inv ; ok, 8*8-Font
lea \inv_16,a2 ; sonst 16 Bytes NOTten
\inv:
not.b (a1)
not.b 0080(a1)
not.b 0160(a1)
not.b 0240(a1)
not.b 0320(a1)
not.b 0400(a1)
not.b 0480(a1)
not.b 0560(a1)
jmp (a2)
\inv_16:
not.b 0640(a1)
not.b 0720(a1)
not.b 0800(a1)
not.b 0880(a1)
not.b 0960(a1)
not.b 1040(a1)
not.b 1120(a1)
not.b 1200(a1)
\inv_end:
bchg #2,(a4) ; Cursorposition invertiert
move.l (a7)+,a2 ; Register zurück
rts
LIGHTMASK: ; Zeichen unter Cursor halbhell darstellen
movem.l d3/d4/a2,-(a7) ; Register retten
lea TCB,a0 ; ^TerminalControlBlock
lea CCB,a4 ; ^CursorControlBlock
move.w #$AA,d3 ; Bitmaske für gerade Pixelzeilen
moveq #$55,d4 ; Bitmaske für ungerade Pixelzeilen
move.l 6(a4),a1 ; abs. Cursor-Adresse
lea \light_end.a2 ; 8*8 annehmen
move.w 36(a0),d1 ; Zeichenhöhe
subq.w #8,d1 ; -Offset für 8*8
beq.s \light ; ok, 8*8-Font
lea \light_16,a2 ; sonst 16 Bytes maskieren
\light:
and.b d3,(a1)
and.b d4,0080(a1)
and.b d3,0160(a1)
and.b d4,0240(a1)
and.b d3,0328(a1)
and.b d4.0400(a1)
and.b d3.0480(a1)
and.b d4.0560(a1)
jmp (a2)
\light_16:
and.b d3.0640(a1)
and.b d4,0720(a1)
and.b d3,0800(a1)
and.b d4.0880(a1)
and.b d3.0960(a1)
and.b d4,1040(a1)
and.b d3.1120(a1)
and.b d4.1200(a1)
\1ight_end:
movem.l (a7)+,d3/d4/a2 ; Register zurück
rts
DEL_SCRUP: ; ESC M'
move.w 6(a0),d1 ; max. Zeile
sub.w 2(a0),d1 ; -akt. Zeile=Anzahl zu scrollender Zeilen
beq.s \del ; schon in letzter Zeile, nur noch löschen
move.l 6(a4),a1 ; abs. Cursorposition
suba.w (a0),a1 ; -akt. Spalte=^Zeilenanfang
bsr.s SCR_UP_ENTRY ; hochscrollen
\del:
move.w 6(a0),d3 ; letzte Zeile
bra DEL_ENTRY ; löschen und zurückkehren
SCROLL_UP: ; Bildschirminhalt incl. der akt. Zeile
; um eine Zeile nach oben schieben
move.l LOGBASE,a1 ; ^Video-RAM
move.w 2(a0).dl ; aktuelle Zeile
beq SCR_UP_END ; nicht scrollen, falls schon oben
SCR_UP_ENTRY: ; Einsprung für E5C 'M'
subq.w #1,d1 ; d1 in dbra-Zähler wandeln
move.w 38(a8),d0 ; Bytes/Textzeile
move.w 3G(a0),d2 ; Zeichenhöhe
lsr.w #2,d2 ; div 4 (es werden immer 4 Pixelzeilen verschoben)
subq.w #1,d2 ; in dbra-Zähler wandeln
move.l a1,a2 ; Zieladresse (a1)
adda.w d0,a2 ; + Bytes/Textzeile = Quelladresse (a2)
movem.l a0/a4/a6,-(a7) ; Register retten
\scr_lns: ; d1+1 Zeilen nach oben scrollen
move.w d2,d0 ; Zeichenhöhe/4-1
\scr_ln: ; 1 Textzeile nach oben scrollen
movem.l (a2)+,REGISTER
movem.l REGISTER,(a1)
movem.l (a2)+,REGISTER
movem.l REGISTER,40(a1) ; 4 komplette Pixelzeilen verschieben
movem.l (a2)+,REGISTER
movem.l REGISTER,80(a1)
movem.l (a2)+,REGISTER
movem.l REGISTER,120(a1)
movem.l (a2)+,REGISTER
movem.l REGISTER,160(a1)
movem.l (a2)+,REGISTER
movem.l REGISTER,200(a1)
movem.l (a2)+,REGISTER
movem.l REGISTER,240(a1)
movem.l (a2)+,REGISTER
movem.l REGISTER,280(a1)
adda.w #320,a1
dbra d0,\scr_ln ; die nächsten 4 Pixelzeilen verschieben
dbra d1,\scr_lns ; nächste Textzeile
movem.l (a7)+,a0/a4/a6 ; Register zurück
SCR_UP_END:
rts
INS_SCRON: ; ESC 'L'
bsr.s SCROLL_DOWN ; Zeile einfügen
bra DEL_LINE ; und löschen
DEL_SCRDN: ; ESC 't'
move.w 2(a0),d1 ; akt. Zeile
beq.s \del ; schon oben, nur noch löschen
move.l 6(a4),a1 ; abs. Cursorposition
suba.w (a0),a1 ; -akt. Spalte=^Zeilenanfang
adda.w 38(a0),a1 ; +Bytes/Textzeile (1 Textzeile tiefer)
suba.w #80,a1 ; -80 Bytes (1 Pixelzeile höher)
bsr.s SCR_ON_ENTRY ; runterscrollen
\del:
clr.w d3 ; oberste Zeile (0)
bra DEL_ENTRY ; löschen und zurückkehren
SCROLL_DOWN: ; Bildschirminhalt incl. der akt. Zeile
; um eine Zeile nach unten schieben
move.l LOGBASE,a1 ; ^Video-RAM
adda.l #32000-80,a1 ; ^letzte Pixel-Zeile
move.w 6(a0),d1 ; max. Zeile
sub.w 2(a0),d1 ; -aktuelle=Anzahl zu scrollender Zeilen
beq SCR_DN_END ; nicht scrollen, falls schon unten
SCR_DN_ENTRY: ; Einsprung für ESC 't'
subq.w #1,d1 ; sonst in dbra-Zähler wandeln
move.w 38(a0),d0 ; Anzahl Bytes/Textzeile * Offset
move.w 36(a0),d2 ; Zeichenhöhe
lsr.w #2,d2 ; div 4 (es werden immer 4 Pixelzeilen verschoben)
subq.w #1,d2 ; in dbra-Zähler wandeln
move.l a1,a2 ; Zieladresse (a1)
suba.w d0,a2 ; - Offset = Quelladresse (a2)
movem.l a0/a4/a6,-(a7) ; Register retten
\scr_lns: ; d1+1 Zeilen nach unten scrollen
move.w d2,d0 ; Zeichenhöhe/4-1
\scr_ln: ; 1 Textzeile nach unten scrollen
movem.l (a2)+,REGISTER
movem.l REGISTER,(a1)
movem.l (a2),REGISTER
movem.l REGISTER,40(a1) ; 4 komplette Pixelzeilen verschieben
suba.w #120,a2
movem.l (a2)+,REGISTER
movem.l REGISTER,-80(a1)
movem.l (a2),REGISTER
movem.l REGISTER,-40(a1)
suba.w #120,a2
movem.l (a2)+,REGISTER
movem.l REGISTER,-160(a1)
movem.l (a2),REGISTER
movem.l REGISTER,-120(a1)
suba.w #120,a2
movem.l (a2)+,REGISTER
movem.l REGISTER,-240(a1)
movem.l (a2),REGISTER
movem.l REGISTER,-200(a1)
suba.w #120,a2
suba.w #320,a1
dbra d0,\scr_ln ; die nächsten 4 Pixelzeilen verschieben
dbra d1,\scr_lns ; nächste Textzeile
movem.l (a7)+,a0/a4/a6 ; Register zurück
SCR_ON_END:
rts
DEL_LINE: ; akt. Zeile löschen, Cursor auf Spalte 0
move.w 2(a0),d3 ; akt. Zeile
DEL_ENTRY: ; Einsprung für Löschen beliebiger Zeilen
move.l LOGBASE,a1 ; ^Video-RAM
addq.w #1,d3 ; ++
move.w 38(a0),d1 ; Bytes/Textzeile
mulu d3,d1 ; (akt. Zeile+1)*(Bytes/Textzeile)
adda.l d1,a1 ; - Bildschirm-Offset
move.w 36(a0),d2 ; Zeichenhöhe
lsr.w #2,d2 ; div 4 (es werden immer 4 Pixelzeilen gelöscht)
subq.w #1,d2 ; in dbra-Zähler Handeln
movem.l a0/a4/a6,-(a7) ; Register retten
mouem.w ZEROES,REGISTER ; Register löschen
\lp:
movem.l REGISTER,-(a1) ; 4 komplette Pixelzeilen löschen
movem.l REGISTER,-(a1)
movem.l REGISTER,-(a1)
movem.l REGISTER,-(a1)
movem.l REGISTER,-(a1)
movem.l REGISTER,-(a1)
movem.l REGISTER,-(a1)
movem.l REGISTER,-(a1)
dbra d2,\lp ; nächste Pixelzeile
movem.l (a7)+,a0/a4/a6 ; Register zurück
clr.w (a0) ; Cursor in Spalte 0
rts ; fertig
BEL: ; Control G (bingggg!)
suba.l a5,a5 ; A5 löschen
move.l BEL_ADR,-(a7) ; Original TOS-Routine
rts ; anspringen
CRS_LEFT: ; ESC 'C' (=Backspace BS)
BS:
tst.w (a0) ; Spalte B?
beq.s \zurück ; ja. BS nicht möglich
subq.w #1,(a0) ; sonst Spalte--
\zurück:
rts ; das war's
CALC_POS: ; nächste Tab-Position berechnen
clr.l d7 ; Default-Rückgabewert; Kein Fehler
move.w d0,d1 ; Spalte retten für Byte-Offset
move.w d0,d2 ; nochmal retten für Bit-Offset
moveq #7,d3 ; Bit-Zähler initialisieren
lsr.w #3,d1 ; Byteoffset berechnen (Spalte div 8)
and.w d3,d2 ; Divisionsrest (Spalte mod 8)
sub.w d2,d3 ; von 7 subtrahiert = Bit-Offset
\bit_lp;
btst d3.0(a1,d1.w) ; Tab gesetzt?
beq.s \weiter ; nein
rts ; sonst zurück
\weiter:
addq.w #1,d0 ; Spalte ++
dbra d3,\bit_lp ; und weitersuchen
\byte_lp:
addq.w #1,d1 ; nächstes Byte des Tabulatoren-Bit-Vektors
cmpi.w #10,d1 ; schon letztes Byte überschritten?
bge.s \fail ; ja, mit Fehler zurück
tst.b 0(a1,d1.w) ; gibt's hier'n Tab?
bne.s \s_next ; yeah!
addq.w #8,d0 ; sonst Spalte +=8 (!@$X"?C-Syntax!)
bra.s \byte_lp ; und nächstes Byte untersuchen
\s_next;
moveq #7,d3 ; wieder beim MSB mit der
bra.s \bit_lp ; Suche beginnen
\fail;
moveq #-1,d7 ; nicht fündig geworden...
rts ; und tschüß
TAB:
lea TABS,a1 ; ^TabulatorenBitVektor
move.w (a0),d0 ; akt. Spalte
addq.w #1,d0 ; ++
cmpi.w #80,d0 ; >=letzte?
bge.s \wrap ; ja, auf Wrap testen
bsr.s CALC_POS ; sonst nächsten Tab suchen
tst.l d7 ; gefunden?
bmi.s \wrap ; nö
move.w d0,(a0) ; sonst Spalte setzen
\ende;
rts ; und zurück
\wrap;
btst #1,8(a0) ; Wrapping erlaubt?
beq.s \ende ; nein, nichts ändern
clr.w d0 ; sonst ersten
bsr.s CALC_POS ; Tabulator finden
tst.1 d7 ; keiner da?
bmi.s \ende ; letztendlich nicht, fertig
move.w d0,(a0) ; sonst Spalte setzen und LF ausführen
LF:
move.w 2(a0),d0 ; akt. Zeile
cmp.w 6(a0),d0 ; 'letzte?
bmi.s \ok ; nein
bsr SCROLL_UP ; sonst scrollen
move.w (a0),-(a7) ; akt. Spalte merken
bsr DEL_LINE ; und letzte Zeile löschen
move.w (a7)+,(a0) ; akt. Spalte zurück
rts
\ok:
addq.w #1,2(a0) ; Zeile++
rts
CR:
clr.w (a0) ; Cursor in Spalte 0 setzen
rts ; fertig
CLS: ; Bildschirm löschen*Home
movem.l a0/a6,-(a7) ; Register retten
moveq #49,d0 ; 50 mal 8 Pixelzeilen löschen
move.l LOGBASE,a6 ; ^Video-RAM
adda.w #32000,a6 ; +32000=Bildschirmende
CLS_ENTRY: ; Entry point für teilweises Löschen
movem.w ZEROES,d1-a2 ; 10 Register löschen
\lp:
movem.l d1-a2.-(a6) ; 8 komplette Pixelzeilen löschen
movem.l d1-a2,-(a6) ; (jeder movem-Befehl löscht 40 Bytes)
movem.l d1-a2,-(a6)
movem.l d1-a2,-(a6)
movem.l d1-a2,-(a6)
movem.l d1-a2,-(a6)
movem.l d1-a2,-(a6)
movem.l d1-a2,-(a6)
movem.l d1-a2,-(a6)
movem.l d1-a2,-(a6)
movem.l d1-a2,-(a6)
movem.l d1-a2,-(a6)
movem.l d1-a2,-(a6)
movem.l d1-a2,-(a6)
movem.l d1-a2,-(a6)
movem.l d1-a2,-(a6)
dbra d0,\lp ; die nächste 10 Zeilen
movem.l (a7)+,a0/a6 ; Register zurück
HOME: ; Cursor nach 0,0
clr.w (a0) ; Spalte 0
clr.w 2(a0) ; Zeile 0
rts
E_BIG: ; Offset-Tabelle für ESC "Großbuchstabe"
dc.w 0 ; A Cursor rauf
dc.w CRS_DOWN-CRS_UP ; B Cursor runter
dc.w CRS_RIGHT-CRS_UP ; C Cursor rechts
dc.w CRS_LEFT-CRS_UP ; D Cursor links
dc.w CLS-CRS_UP ; E Bildschirm löschen
dc.w BIG_FONT-CRS_UP ; F 8xl6-Font aktivieren
dc.w SML_FONT-CRS_UP ; G 8x8-Font aktivieren
dc.w HOME-CRS_UP ; H Home
dc.w CRS_UP_SCR-CRS_UP ; I Cursor rauf (Scrollen, falls in Zeile 0)
dc.w DEL_FROM_CRS-CRS_UP ; J Löschen ab Cursor bis Bildschirm-Ende
dc.w L_TO_END-CRS_UP ; K Löschen ab Cursor bis Zeilenende
dc.w INS_SCRDN-CRS_UP ; L Zeile einfügen. Rest runterschieben
dc.w DEI_SCRUP-CRS_UP ; M Zeile löschen, Rest hochscrollen
dc.w DEF_TABS-CRS_UP ; N Tabulatoren an jede 8. Spalte setzen
dc.w CLR_TAB5-CRS_UP ; O alle Tabulatoren löschen (Tabula rasa)
dc.w SET_TAB-CRS_UP ; P Tabulator setzen
dc.w CLR_TAB-CRS_UP ; Q Tabulator loschen
dc.w LIGHT_ON-CRS_UP ; R halbe Helligkeit einschalten
dc.w LIGHT_OFF-CRS_UP ; S volle Helligkeit einschalten
dc.w FSCR_LEFT-CRS_UP ; T akt. Zeile um 1 Pixel nach links scrollen
dc.w FSCR_RIGHT-CRS_UP ; U akt. Zeile in 1 Pixel nach rechts scrollen
dc.w FSCRON-CRS_UP ; V incl. akt. Zeile pixelweise n. unten scrollen
dc.w FDSCRUP-CRS_UP ; W akt. Zeile löschen, Rest pixelweise hochscrollen
dc.w FSCRUP-CRS_UP ; X incl. akt Zeile pixelweise n. oben scrollen
dc.w DIR_CRS_CRS_UP ; Y Cursor positionieren (Zeile, Spalte)
dc.w FDSCRDN_CRS_UP ; Z akt. Zeile löschen, Rest pixelweise runter
E_SML: ; Offset-Tabelle für ESC "Kleinbuchstabe"
dc.w CRS_ON_T-SET_COLOR ; a Cursor einschalten, falls ESC 'f'-Zähler=0
dc.w 0 ; b Vordergrundfarbe setzen
dc.w 0 ; c Hintergrundfarbe setzen
dc.w DEL_TO_CRS-SET_COLOR; d von Bildschirmanfang bis incl. Cursor löschen
dc.w CRS_ON-SET_COLOR ; e Cursor einschalten. ESC ’f'-Zähler löschen
dc.w CRS_OFF-SET_COLOR ; f Cursor ausschalten
dc.w GET_TCB-SET_COLOR ; g Pointer auf TerminalControlBlock holen
dc.w GET_CCB-SET_COLOR ; h Pointer auf CursorControlBlock holen
dc.w RESETCO-SET_COLOR ; i Terminal initialisieren
dc.w SAVE_CRS-SET_COLOR ; j Cursorposition speichern
dc.w REST_CRS-SET_COLOR ; k Cursor an zuletzt gemerkte Stelle
dc.w DEL_LINE-SET_COLOR ; l Zeile löschen
dc.w WHITE_BLK-SET_COLOR ; m weiße Schrift auf schwarzem Grund
dc.w BLK_WHITE-SET_COLOR ; n schwarze Schrift auf weißem Grund
dc.w L_TO_CRS-SET_COLOR ; o von Zeilenanfang bis incl. Cursor löschen
dc.w INV_ON-SET_COLOR ; p inverse Darstellung ein
dc.w INV_OFF-SET_COLOR ; q inverse Darstellung aus
dc.w GRAPHMODE-SET_COLOR ; r Grafikmodus
dc.w INS_SCRUP-SET_COLOR ; s Zeile einfügen, Rest hochschieben
dc.w DEL_SCRDN-SET_COLOR ; t Zeile löschen. Rest runterschieben
dc.w SCR_LEFT-SET_COLOR ; u akt. Zelle um 1 Spalte nach links scrollen
dc.w WRAP_ON-SET_COLOR ; v automatischen Zeilenüberlauf einschalten
dc.w WRAP_OFF-SET_COLOR ; h automatischen Zeilenüberlauf ausschalten
dc.w SCR_RIGHT-SET_COLOR ; x akt. Zelle um 1 Spalte nach rechts scrollen
dc.w UNDER_ON-SET_COLOR ; y Unterstrich ein
dc.w UNDER_OFF-5ET_COLOR ; z Unterstrich aus
CRS_ON_T: ; ESC 'a'
tst.b 1(a4) ; liegt kein ESC 'f'-Aufruf vor?
beq.s \ein ; nein, einschalten
subq.b #1,1(a4) ; sonst Anzahl der Aufrufe dekrementieren
bne.s \zurück ; nur bei 0 einschalten
\ein:
bset #3,(a4) ; Cursor einschalten
\zurück:
rts
SET_COLOR: ; ESC 'b'/'c' (nicht unterstützt)
lea VEC_BASE,a1 ; Vektor
lea SET_COLOR2,a2 ; auf Holen des Farbwertes
move.l a2,(a1) ; umschalten
rts
SET_COLOR2:
lea VEC_BASE,a1 ; Vektor wieder
lea STD_VEC,a2 ; auf normale Ausgabe
move.l a2,(a1) ; umschalten
rts
CRS_ON: ; ESC e'
bset #3,(a4) ; Cursor einschalten
clr.b 1(a4) ; Anzahl CRS_0FF löschen
rts
CRS_OFF: ; ESC 'f'
bclr #3,(a4) ; Cursor ausschalten
addq.b #1,1(a4) ; Anzahl der Aufrufe inkrementieren
rts
INV_ON: ; ESC 'p'
bset #0,8(a0) ; inverse Darstellung ein
rts
INV_OFF: ; ESC 'q'
bclr #0,8(a0) ; inverse Darstellung aus
rts
WRAP_ON: ; ESC 'v'
bset #1,8(a0) ; Wrapping einschalten
rts
WRAP_OFF: ; ESC 'w'
bclr #1,8(a0) ; Wrapping ausschalten
rts
UNDER_ON: ; ESC 'y'
bset #2,8(a0) ; Unterstrich einschalten
rts
UNDER_OFF: ; ESC 'z'
bclr #2,8(a0) ; Unterstrich ausschalten
rts
INS_SCRUP: ; ESC 's'
bsr SCROLL_UP ; Zeile einfügen, Rest hochschieben
bsr DEL_LINE ; und neue Zeile löschen
rts