int21h

Klávesnice a myš v Pascalu

Náš trojdílný seriál zakončíme dvěma díly pohromadě, protože klávesnice a myš k sobě dnes již neodmyslitelně patří, ať už se jedná o standardní práci s počítačem (kde se spíše využívá myš), psaní textů (kde je naopak sahání po myši nežádoucí) či hraní her (kde jsou vytíženy obě na maximum). Začneme asi tím nejjednodušším, a to je myš. Možná se Vám bude zdát, že to není pravda, ale uvidíte, že kompletně ovládat myš je na assembler mnohem snažší než hákovat přerušení od klávesnice a hlavně jej číst. A nemusím doufám podotýkat, že příklady s přerušením vyžadují jednotku DOS a u Free Pascalu ještě GO32. Pokud Vám u některých příkladů nebude fungovat kombinace "interrupt; assembler; far;", tak to buď napište jako "interrupt assembler far;" a nebo napište jen "interrupt; assembler;" a před řádek FUNCTION dejte {$F+} a za řádek END; dejte {$F-}. Efekt to bude mít stejný.

MYŠ:

Myš se dnes připojuje k portu PS/2, ty novější i k USB a ty starší ještě ke COMu. Vždy ale platí, že pokud je určena pro DOSové aplikace, tak tu musí být ovladač (alespoň pro COM a PS/2), který sídlí na přerušení INT 33h a tam poskytuje své služby. Není tedy nic jednoduššího, než prostě toto přerušení volat a dané informace si získat. Začneme v TP7, protože je to jednodušší na vysvětlení. Předně, než začnete myš používat, měli byste jí resetovat. Jsou dva typy resetů. Ten "teplý" (softwarový) je rychlejší, ale může se stát, že ho některé myši neakceptují. Pak je tu "studený" (hardwarový), který je pomalý, ale za to spolehlivý. Většinou stačí myš resetovat jen jednou, takže je možné provést ten studený. Nebo, zkusit teplý a pokud se myš nechytne, pak studený. A později pak už přímo ten, na který se myš ozvala. Tato funkce je také důležitá v tom, že nám vrací i počet tlačítek, které může myš v současné době používat (to ovšem neznamená, že jich nemá víc).

function ResetMysi : word; assembler;
asm
	xor	ax,ax			{číslo služby: 0-HW, jinak 21h-SW reset}
	int	33h			{volání ovladače myši na INT 33h}
	or	ax,ax			{je AX = 0? (Rychlejší nez CMP AX,0?)}
	jne	@ok			{není, v pořádku, myš je přítomná}
	mov	bx,128			{myš není, nastavíme počet tlačítek na 128}
@ok:
	mov	ax,bx			{vrátíme zjištěný počet tlačítek}
end;

Pokud je počet tlačítek 1, 2 nebo $ffff (je vrácen v BX), jedná se o dvoutlačítkovou myš. Pokud je ovšem AX po provedení služby rovno 0, není myš nainstalována (pozor! U služby 21h SW Reset musí být pro správné vykonání v AX $21 před i po INT 33!). Služba 0 má samozřejmě také více funkcí, které provádí, ale ty si můžete najít v ATHelpu v popisu daného přerušení. Občas se stane, že na adrese, kde by měl být ukazatel na ovladač myši, je 0:0 a tedy pokud byste zavolali INT 33h, pravdpodobně byste skončili v Resetu počítače nebo úplně jinde... Proto je vhodné nejprve ještě otestovat, zda ta adresa ukazuje na smysluplnný kód:

function ResetMysi : word; assembler;
asm
	xor	ax,ax
	mov	ds,ax
	mov	si,33h*4
	cmp	word ptr ds:[si+2],0	{ukazuje do segmentu 0, tedy blbost}
	jz	@neni
	lds	si,dword ptr ds:[si]
	cmp	byte ptr ds:[si],0cfh	{na adrese je RETF, ovladač není instalován}
	jz	@neni
	xor	ax,ax
	int	33h
	or	ax,ax
	jne	@ok
@neni:
	mov	bx,128
@ok:
	mov	ax,bx
end;  

A teď, jak zjistit, kde se myš nachází a jaká má zrovna stisknutá tlačítka? Ovladač má na toto všechno jednu funkci, která zjistí vše potřebné. Avšak my většinou potřebujeme číst jen jednu proměnnou, takže ji musíme rozepsat do třech funkcí:

function MysX : word; assembler;
asm
	mov	ax,3
	int	33h
	mov	ax,cx
end;


function MysY : word; assembler;
asm
	mov	ax,3
	int	33h
	mov	ax,dx
end;


function MysTlac : word; assembler;
asm
	mov	ax,3
	int	33h
	mov	ax,bx
end;  

Jen pár poznámek. Souřadnice myši jsou vztažené ke standardnímu grafickému režimu, který je standardně 320x200 (pokud jej máte nastaven) nebo 640x200 a v tom je dneska problém, neboť většina z nás určitě využívá větší rozlišení, na které myš v určitém slova smyslu kašle. Jak to změnit si povíme později. Pokud pracujete v textovém režimu, tak musíte všechny souřadnice myši vydělit 8 pixely (resp. 16 v ose Y), a tedy upravit funkce takto:

function MysXtext : word; assembler;
asm
	mov	ax,3
	int	33h
	mov	ax,cx
	shr	ax,3
end;


function MysYtext : word; assembler;
asm
	mov	ax,3
	int	33h
	mov	ax,dx
	shr	ax,4
end;  

Tím dostanete pozici ne v pixelech, ale v řádcích a sloupcích textu. A co se týče funkce pro tlačítka, tak tady platí, že pokud je bit = 1, je tlačítko stisknuté, pokud 0, je zrovna puštěné. Levé tlačítko má vždy bit 0, pravé bit 1 a prostřední bit 2. Pokud chcete zjistit dvojklik, musíte testovat tlačítka postupně a zjišťovat, jestli bylo uvolněno a stisknuto, nebo použít funkce pro zjištění počtu uvolnení a stisku tlačítka (viz. funkce 5 a 6 v ATHelpu u přerušení $33). Pokud pracujete v textovém nebo nižším grafickém režimu (většinou jen 16 barev), může ovladač zajišťovat zobrazování kurzoru myši na obrazovce sám. K tomu existují dvě funkce, které skryjí a nebo zobrazí kurzor. Nutno podotknout dvě věci: některé ovladače mají počitadlo, tedy pokud např. 2x po sobě kurzor skryjete, musíte ho poté 2x zobrazit, než bude vidět. Jiné toto ignorují a berou vždy jen první výskyt volání takové funkce, proto je ideální to volat vždy jen 1x a nastřídačku. Ta druhá věc je, proč to vlastně používat. Jde o to, že obrazovku si jistě vykreslujete sám, ale kurzor kreslí ovladač. V tomto případě předtím, než začnete kreslit, musíte myš schovat, a až kreslení dokončíte, ji můžete zobrazit. Pokud byste to neudělali zrovna takto, tak poté, co byste dokreslili a někdo by hýbnul myší, tak by ovladač automaticky obnovil znak (či skupinu pixelů) po kurzorem na ten původní (který si ovšem načetl ještě předtím, než jste něco nakreslili) a pak by přesunul kuzor jinam. Výsledkem by bylo, že pod kurzorem by se objevilo to, co tam bylo před tím, než jste to smazali překreslením.

procedure Zobraz; assembler;
asm
	mov	ax,1
	int	33h
end;


procedure Skryj; assembler;
asm
	mov	ax,2
	int	33h
end;  

Jak vidíte, není to žádná věda. Menší problém bude asi v tom, že Vám bude kurzor jakoby blikat, když budete kreslit. Ale to vyřešíme později. Některé z Vás ale může třeba nudit volat neustále tyto funkce. Můžete se tzv. pověsit na časovač, který standardně 18.2x za vteřinu zjistí informace o myši a uloží je do nějakých Vašich proměnných. Tato metoda není ideální, neboť se myš zbytečně volá X krát za vteřinu, ale umožní nám se naučit používat přerušení, které později využijeme u klávesnice. Nejprve si musíme nadefinovat proměnné, které budeme potřebovat:

var	X,Y,Tlac,Back : word;  

Asi se divíte, k čemu je ta poslední proměnná. Ta obsahuje minulý stav tlačítek, který použijeme pro zajímavý trik. Jistě víte, že když kliknete myší na nějaké tlačítko ve Windows či Linuxu, tak se akce provede až poté, co tlačítko pustíte. Pokud jej stále držíte a přesunete se jinam, akce se neprovede. A to se nám nyní povede udělat (lze to samozřejmě udělat i s pomocí předchozích funkcí). Předpokládám, že víte, jak se dělá přerušovací rutina v Turbo Pascalu 7:

procedure InfoMys; interrupt; assembler; far;
asm
	mov	ax,back		{záloha předchozího stavu tlačítka}
	mov	back,ax
	mov	al,20h		{resetování řadiče přerušení}
	out	20,al      
	mov	ax,3
	int	33h
	mov	X,cx
	mov	Y,dx
	mov	Tlac,bx
	call	Int1CSave	{voláme původní obsluhu časovače}
end;  

Teď nám stačí upravit stávající funkce a připsat si některé další:

function MysX : word; assembler;
asm
	mov	ax,x
end;


function MysY : word; assembler;
asm
	mov	ax,y
end;


function MysTlac : word; assembler;
asm
	mov	ax,tlac
end;  

Pokud pracujete ve Free Pascalu, je vhodné tyto funkce napsat jako inline. Nové funkce otestují, zda je konkrétní tlačítko stisknuté, a ta druhá navíc otestuje, zda jej uživatel již pustil. Ta první se hodí, pokud chcete myší kreslit, ta druhá, pokud chcete ovládat tlačítka na ploše stejně jako v GUI. Číslo tlačítka je 1 pro levé, 2 pro pravé a 4 pro prostřední (i pokud je prohodíte, stále je 1=levé, i když je primární pravé!).

function Tlac(Cislo : word) : boolean; assembler;
asm
	mov	bx,cislo
	xor	al,al
	test	tlac,bx
	jz	@skok
	inc	al
@skok:
end;


function Stisk(Cislo : word) : boolean; assembler;
asm
	mov	bx,cislo
	xor	al,al
	test	back,bx
	jz	@konec
	test	tlac,bx
	jnz	@konec
	inc	al
@konec:
end;  

Myš nám zobrazuje nějaký kurzor. Co když ale chceme vlastní? K tomuto slouží funkce $9 a $A na INT 33h. Protože se ale moc často nepoužívají, nebudu je zde uvádět (pokud Však o ně máte zájem, ATHelp je Vám k dispozici). Snad jen podotknu, že se kurzor vždy kreslí pomocí masek AND a XOR. Nejprve se aplikuje AND, tj. pokud je daný bit v masce 1, převezme se jeho hodnota z pozadí, jinak se nastaví na 0. Poté se aplikuje XOR, které změní všechny bity z 0 na 1 a obráceně. Tedy může buď zobrazit samotný kurzor (pokud je maska AND rovna 0), nebo např. invertovat pozadí (pokud je celá AND rovna 1), vždy pro celou XOR masku rovnou 1 (resp. $ff na BYTE). Grafický kurzor má dvě masky o šířce 16x16 pixelů v 1 bitové barvě, tedy celkem 64 bytů (2 sady po 16 řádcích, na každém z nich 16 bitů, nebo-li 2 byty). Textový kurzor může být hardwarový, tj. blikající, kde nastavujete první a počáteční linku, viz. Klávesnice a její kurzor) nebo softwarový, kdy se nejprve aplikuje maska AND na znak a atribut, a poté maska XOR na to samé. Pokud např. chcete jen invertovat barvu znaku včetně jeho pozadí a jej samotný ponechat, nastavte AND pro atribut na 0, pro znak na $ff, a pro XOR to dejte opačně (v tomto případě se ale změní ovšem i bit blikání, resp. rozšířené barvy pozadí, pokud je nastavena). Ve většině grafických aplikací se však používá kurzor vlastní, kdy je myš skryta a programátor si od ní bere jen souřadnice a stav tlačítek. Samotný kurzor si kreslí sám jako obrázek (popř. ho postupně mění a tím dostane animaci). Při prvním spuštění programu, resp. změně grafiky si načtěte pozadí pod prvním objevením myši. Resp. uschovejte si oblast video paměti o velikosti kurzoru. Poté na toto místo vykreslete myš. Když se myš pohne, obnovte pozadí tím uschovaným a celou sekvenci opakujte pro nové souřadnice. Velice snadné a přitom efektivní.

Jak vidíte, je to celkem jednoduché. Jak ale vytvořit tlačítka na ploše, na která by myš mohla klikat? Jednoduše. Každé z nich bude mít 5 stavů: tlačítko není vidět, tlačítko je vidět, myš je nad tlačítkem (mohlo by se zesvětlit), myš stiskla tlačítko (mohlo by se prohnout, pokud myš najede mimo tlačítko, to se změní dočasně na "tlačítko je vidět", pokud stále držíme tlačítko a vrátíme se nad tlačítko, to se opět prohne, ale žádnou akci stále nevyvolá, dokud tlačítko také nepustíme nad jeho oblastí), a myš stiskla a pustila tlačítko nad tlačítkem (vyvoláme akci). Každé bude reprezentováno nějakým obrázkem (např. BMP), pokud to nebude textový režim. A vždy budete cyklicky testovat všechna nadefinovaná a podle jejich aktuálního stavu provedete nebo neprovedete nějakou akci (např. jejich vykreslení).

Jak ale zařídit, aby nám naše funkce pro přerušení zjišťovala dané hodnoty? Musíme ji na dané přerušení tzv. pověsit. Zvolíme si časovač $1C, neboť ten je pro tyto účely nejvhodnější. Předtím ale musíme uschovat adresu té rutiny, která tam už je, protože v tomto případě např. zajišťuje nastatovávání systémového času nebo zastavování disketové mechaniky. A abychom nemuseli před koncem programu myslet na to, že ji musíme zase odstranit, necháme Turbo Pascal, aby to provedl za nás. Stačí, když naši uklídovou proceduru pověsíme na výstupní kód programu.

var	Int1CSave : pointer;
	KonecProc : pointer;
	Tlacitek : word;
	Mys : boolean;


{$F+}
procedure KonecMyse;		{uklízecí procedura}
begin
 ExitProc := KonecProc;
 SetIntVec($1C,Int1CSave);
end;
{$F-}


procedure InitMys;		{instalační procedura}
begin
 Tlacitek := ResetMysi;
 Mys := Tlacitek <> 128;
 if Mys then
 begin
  GetIntVec($1C,Int1CSave);
  SetIntVec($1C,Addr(InfoMys));	{místo ADDR můžeme také použít @}
  KonecProc := ExitProc;
 end;
end;  

Zde se používají funkce jednotky DOS, takže jí musíme přidat do USES, jinak nám to nebude fungovat (pokud nechcete používat tuto jednotku, můžete si na mých WWW stránkách stáhnout její náhradu, která je sice zatím jen beta, ale základní funkce u ní už fungují, takže si je můžete zkopírovat). Pokud nerozumíte některým příkazům, není nic jednoduššího než si otevřít nápovědu přímo v Pascalu. Možná se ptáte, proč tu dosud není žádný kód ve Free Pascalu. To proto, protože jsme se stále nedostali k finálnímu kódu. Kód výše sice bude fungovat, ale rozhodně není moc vhodný, protože zbytečně zatěžuje procesor. Ale ten až později. Nejprve ještě to slíbené řešení s tím rozsahem souřadnic. Proč by někdo chtěl měnit citlivost myši? Jsou minimálně dvě příčiny. Buď se Vám myš pohybuje např. jen v horním levém výřezu obrazovky a nebo se pohybuje po příliš velkých skocích. Naštěstí i ovladač myši na toto myslí a proto můžete tyto parametry změnit. Pokud chcete např. zjemnit pohyb myši, stačí jen změnit citlivost. Ta se nastavuje tzv. jednotkách Mickeys/pixel. Jeden Mickey je 1/200 palce, tj. 0.0127 cm. Čím vyšší nastavíte číslo pro X nebo Y, tím rychleji se bude myš hýbat. Kromě rychlosti se nastavuje ještě zrychlení, tj. rychlost, při jejímž překročení se pohyb myši dočasně 2x zrychlí. Pokud jej nastavíte na 0, jedná se o 64. Tenkrát jde ale o mickeys/vteřinu ne na pixel. 64 mickeys/vteřinu se rovná asi 8 mm za vteřinu. Protože ale další programy před Vámi mohou mít nastavenu svou vlastní citlivost, měli byste při startu nejprve tu starou uschovat, pak nastavit svoji a před koncem programu obnovit tu původní.

procedure NastavCitlivost(X,Y,R : word); assembler;
asm
	mov	ax,1ah
	mov	bx,x		{v zásobníku jsou hodnoty}
	mov	cx,y
	mo	dx,r
	int	33h
end;


procedure CtiCitlivost(var X,Y,R : word); assembler;
asm
	mov	ax,1bh
	in	33h
	les	di,x
	mov	es:[di],bx	{v zásobníku jsou ukazatele/adresy}
	les	di,y
	mov	es:[di],cx
	les	di,r
	mov	es:[di],dx
end;  

Ale stále jsme nevyřešili to, že nám myš skáče po velkých skocích, nebo že se pohybuje v příliš malém prostoru. To řeší funkce pro nastavení rozsahu. Ta používá minimální a maximální souřadnice, ve kterých se bude myš moci pohybovat. Standardně je rozsah 639x199, tj. až do tohoto rozlišení +1 je schopna myš se teoreticky pohybovat po 1 pixelu (v ideálním případě). Pokud Však máte vyšší rozlišení nebo se Vám myš i tak hýbe přes více pixelů i při sebe jemnějším pohybu, musíte změnit rozsah. První mez necháme na 0, ostatní nastavíme na velikost daného rozlišení. Takže za souřadnice X a Y dosaďte své rozlišení, např. 800x600.

procedure NastavRozsah(X,Y : word); assembler;
asm
	mov	ax,7
	xor	cx,cx
	mov	dx,x
	dec	dx
	int	33h
	inc	ax
	mov	dx,y
	dec	dx
	int	33h
end;  

Pokud se Vám přesto zdá, že myš má citlivost stále malou, vynásobte souřadnice x2 nebo 4x (pak ale musíte také ty, které Vám myš vrátí tímto číslem vydělit, a také byste ji měli trochu zrychlit):

procedure NastavRozsah(X,Y : word); assembler;
asm
	mov	ax,7
	xor	cx,cx
	mov	dx,x
	shl	dx,1
	dec	dx
	int	33h
	inc	ax
	mov	dx,y
	shl	dx,1
	dec	dx
	int	33h
end;  

Citlivost a rozsah je vhodné nastavovat až po změně rozlišení. Avšak některé druhy myší nezávisle na ovladači rozsah stejně nezmění. Naštěstí to jsou výjimky. V poslední době se do módy dostávají tzv. myši s kolečkem. Myslíte si, že to je výsada jen Windows? Omyl. Ovladač myši by měl kolečko podporovat také (ovšem většinou asi ne v DOSu, resp. pod zastaralým ovladačem). Nejprve se musíme přesvědčit, zda myš kolečko opravdu podporuje. To se provede následovně:

function Kolecko : boolean; assembler;
asm
	xor	dx,dx
	mov	ax,$3f
	int	33h
	cmp	ax,574dh
	jne	@konec		{API pro kolečko je přítomno}
	test	cx,1
	jz	@konec
	inc	dl		{dokonce i myš má kolečko}
@konec:
	mov	al,dl
end;  

A teď, jak to kolečko vlastně přečíst. Pozice kolečka se čte stejnými funkcemi, jako stav normálních tlačítek a jejich os. Jeho hodnota se vrací v BH jako 8 bitové číslo se znaménkem (tj. ShortInt). A jedná se o tzv. naakumulovanou hodnotu od posledního čtení. Tedy pokud přečtete např. 10, a kolečko se pak nepohne, měla by tam příště být 0. Naopak pokud ho nebudete číst dostatečně často, může se stát, že se kolečko pohne tam a sem, překročí 2x nulu, ale Vy zjistíte jen ten poslední pohyb, resp. jejich součet. Kladná hodnota znamená, že se kolečko točilo obráceně, záporná normálně. Jeho hodnota se získá snadno:

function PohybKol : shorint;
asm
	mov	ax,3
	int	33h
	mov	al,bh
end;  

Teď už máme snad všechno. Je ale čas to přepsat na něco rychlejšího. Proč např. testovat neustále, zda se pohnula myš, když to může za nás udělat sám ovladač. Říká se tomu program na řízení událostí. Jednoduše se pověsíme na daný ovladač a on nás sám zavolá, když se něco semele. Jinými slovy, pokud se myš nehýbe, program si jí nevšímá a neztrácí čas testováním. Funkci musíme napsat se vzdáleným modelem, protože ovladač myši nemusí být v našem segmentu (a na 99% ani nebude). Buď to přikážeme Pascalu nebo si nakonec doplníme sami instrukci RETF. A aby nám myš neničila naše registry, napíšeme danou proceduru jako přerušení, čímž si pomůžeme ještě v jedné věci: TP7 za nás uschová registry. Pokud máme vracet nějaká data, musíme znát registr DS, ten nám ovšem myš laskavě přepíše na svůj segment. Co s tím? Musíme si ho někam schovat a to ještě, než vstoupíme do naší rutiny. Náš program bude vypadat nějak takto:

var Kolecko : shorint;


procedure Handler; assembler; interrupt; far;
asm
	push	ds		{oblast dat pro myš}
	mov	ax,$f000	{viz.Install}
	mov	es,ax		{maska událostí je nám celkem volná}
	mov	di,$fffc
	mov	ax,es:[di]	{získáme náš DS, pokud nám ho někdo nepřepsal}
	mov	ds,ax
	mov	ax,tlac
	mov	back,ax
	mov	tlac,bx
	mov	kolecko,bh
	mov	x,cx
	mov	y,dx
	pop	ds		{vrátíme myši její DS, kdyby ho ještě chtěla}
end;				{spíše ho ale přepíše POPA z TP7 kvůli "přerušení"}  

Ty instrukce PUSH a POP s DS tam jsou celkem zbytečné a myslím, že se vůbec nic nestane, když tam nebudou. Náš program musíme nejprve ale nainstalovat a po skončení našeho programu zase odinstalovat, jinak bude totiž fungovat i dál, bez nás a to nedopadne dobře:

var Puvodni : pointer;
    PuvMask : word;


procedure Install(Adresa : pointer); assembler;
asm
	mov	ax,$14
	mov	cx,$ff		{volání při všech událostech, včetně kolečka na bitu 7)
	mov	dx,adresa.word[2]
	mov	es,dx		{do seg.reg.lze přímo dát jen konstantu}
	mov	dx,adresa.word[0]
	int	33h		{uschováme původní handler}
	mov	puvodni.word[0],dx
	mov	puvodni.word[2],es
	mov	puvmask,cx
	mov	ax,$f000
	mov	es,ax
	mov	di,$fffc	{na adrese $f000:$fffc by teoreticky nic být nemělo}
	mov	ax,ds		{pokud ano, tak třeba ještě 0:$4f0}
	mov	es:[di],ax	{uschováme si svůj DS kvůli myši}
end;


procedure UnInstall; assembler;
asm
	mov	ax,$14		{obnovíme původní obslužný program}
	mov	cx,puvmask
	mov	dx,puvodni.word[2]
	mov	es,dx
	mov	dx,puvodni.word[0]
	int	33h
end;  

Toto je sice dobré řešení (za předpokladu, že si zvolíme takovou adresu, kterou nám nikdo nebude přepisovat), ale něco tomu stále chybí (je to moc složité a navíc "prasárna"). Kdyby se Vám zdálo, že to je příliš zmatené, tak můžete zkusit ještě tuto variantu, která by údajně měla také celkem slušně fungovat:

procedure Install(Adresa : pointer); assembler;
asm
	mov	ax,$c
	mov	cx,$ff
	les	dx,adresa
	int	33h
end;


procedure Handler; assembler; interrupt; far;
asm
	mov	si,seg @data
	mov	ds,si
	mov	si,tlac
	mov	back,si
	mov	tlac,bx
	mov	x,cx
	mov	y,dx
end;


procedure UnInstall; assembler;
asm
	mov	ax,$c
	mov	cx,0
	int	33h
end;  

Tak, a už by nám to mělo fungovat. Tady bychom mohli klidně skončit. Pokud chcete ale znát více, čtěte dále.. Cco kdybychom si třeba napsali celý svůj celý ovladač na myš? Nejprve si definujeme některé nové proměnné (X,Y a TLAC necháme z minula):

const	COM1INTR = $0c;
	COM1PORT = $3f8;
var	MouseHandler : pointer;		{Můžete použít i typ PROCEDURE}
	ComBuffer : array[0..2] of byte;
	pocet : byte;  

Abychom byli korektní, necháme si bázovou adresu sdělit přímo od BIOSu. Tedy řádek s COM1PORT přepíšeme takto:

var	COM1PORT : word absolute 0:$400;  

A teď, jak to celé funguje. Protože geniální věci bývají prosté a protože je myš geniální věc, funguje naprosto jednoduše: pokud se myš pohne, pošle po sériovém portu většinou 3 byty informací (některé myši 4 až 6). Takže stačí tyto informace pouze zpracovat. Protože se později navěsíme na obsluhu sériového portu, budeme mít myš jen sami pro sebe. Je nutné nejprve vědět, co nám ta myš vlastně posílá (podmínkou ovšem je, aby byla na sériovém portu! Na PS/2 myši toto zabírat nebude! Leda by byly tak inteligentní a jejich ovladač by simuloval i myš na sériovém portu, ovšem, proč by to dělal?). Myš typu MICROSOFT posílá standardně 3 byty po sobě:

        bit:    7  6  5  4  3  2  1  0
byte 1  (sync)  0  1  L  R  y7 y6 x7 x6
byte 2  (dX)    0  0 x5 x4  x3 x2 x1 x0
byte 3  (dY)    0  0 y5 y4  y3 y2 y1 y0  

První byte je takzvaný synchronizační. Pokud je jeho bit 6 = 1 (v tomto případě nejvyšší), pak musíte začít počítat byty od 0. Zároveň v něm také najdete první dvě tlačítka myši (1=tlačítko je puštěné!). V ostatních bytech jsou relativní souřadnice myši od posledního volání (+ je doleva nebo dolů podle osy). Pokud je MSB v daném bytu (po složení) = 1, jedná se o záporné číslo. Aby to nebylo tak jednoduché, myši značky MOUSE SYSTEMS posílají 5 bytů (poznají se tak, že mají nejvyšší bit (zde 7!) prvního byte roven 1 a 2 dvojice stejných bytů). Ty mají o trochu lépe organizovaná data, navíc umožňují čtení i středního (MB) tlačítka, avšak pozor, tentokrát 1=stisknuté. Co se týče pohybu, tak tady zase pro změnu + znamená vždy doprava nebo nahoru. A nesmíme zapomenout na myši, které posílají ještě informace o kolečku (který se posílá jako extra byte navíc, tedy ze 3 máte hned 4).

byte 1		 1     0     0     0     0    LB    MB    RB
byte 2		X7    X6    X5    X4    X3    X2    X1    X0
byte 3		Y7    Y6    Y5    Y4    Y3    Y2    Y1    Y0
byte 4	stejný jako byte 2
byte 5  stejný jako byte 3  

Navíc musíte mít správně nastaven COM port:

  Microsoft Mouse        1200 bps, 7 data bitů, 1 stop bit, bez parity
  Mouse Systems Mouse    1200 bps, 8 data bitů, 1 stop bit, bez parity

To se dělá pomocí přerušení $14 a služby 0:

procedure Nastav(info : byte); assembler;
asm
	mov	dx,1		{první COM port}
	xor	ax,ax
	mov	al,info
	int	14h
end;  

Kde BYTE "info" má toto složení:

	bity 7-5:	rychlost (pro myši 100)
	bity 4-3:	parita (x0=žádná, 01=lichá, 11=sudá)
	   bit 2:	počet stop bitů minus 1
	bity 1-0:	délka slova/bytu (10=7 bitů, 11=8 bitů)  

A teď už konečně napíšeme tu funkci, když už víme, jak na to. Pro zjednodušení budeme brát jen myši typu MS, protože většinou pod DOSem jiné neuvidíme (až na výjimky):

procedure MyMouseHandler; Interrupt; far;
var dx, dy : integer;
    inbyte : byte;
begin
 inbyte := Port[COM1PORT];		{přečteme, co nám myš chce}
 if inbyte and 64 = 64 then pocet := 0;	{je to první byte}
 ComBuffer[Pocet] := inbyte;		{uložíme data}
 inc(Pocet);
 if Pocet = 3 then			{už máme všechny tři byty}
 begin
  dx := (ComBuffer[0] and 3) shl 6 + ComBuffer[1];
  dy := (ComBuffer[0] and 12) shl 4 + ComBuffer[2];
  if dx >= 128 then Dec(dx,256);
  if dy >= 128 then Dec(dx,256);	{musíme převést číslo na záporné}
  inc(x,dx);
  inc(y,dy);				{aktualizujeme souřadnice a tlačítka}
  Tlac := (ComBuffer[0] and 32 <> 32) or
	  ((ComBuffer[0] and 16 <> 16) shl 1);
  Pocet := 0;
 end;
 Port[$20] := $20;			{potvrdíme, že jsme zpracovali 8 bitové přerušení}
end;  

A teď ještě procedury, jak náš ovladač nainstalovat a po skončení práce v našem programu zase odinstalovat:

procedure InitDriver;			{nainstalujeme si svůj vlastní ovladač}
begin
 asm
	xor	ax,ax
	int	33h			{je vhodné provést alespoň reset myši}
 end;
 Pocet := 0;
 X := 0;				{nastavíme souřadnice myši}
 Y := 0;
 Tlac := 0;				{a také její tlačítka}
 GetIntVec(COM1INTR,MouseHandler);
(*
 GetIntVec(COM1INTR,@MouseHandler);	{pokud bychom MMH definivali jako PROCEDURE.}
					{pak jej ale můžeme volat i v TP7 bloku}
					{pointer můžeme volat jen v ASM pomocí CALL}
*)
 SetIntVec(COM1INTR,@MyMouseHandler);
end;


procedure ZrusDriver;		{odinstalujeme svůj ovladač}
begin
 SetIntVec(COM1INTR,MouseHandler);
end;  

Tak, jsem si jist, že jste to všechno dobře pochopili :-) a tedy si třeba i vybrali variantu, kterou si můžete přepsat do Free Pascalu. Změn nebude potřeba mnoho, jen co se týče adresování, nesmíte zapomenout, že neexistují segmenty, DOSová paměť se adresuje přes selektor FS, pole PORT se musí nahradit OutPortX nebo InPortX a funkce nastavující vektory se změní na set_pm_interrupt, resp. get_pm_interrupt (ať už se jedná o softwarové nebo hardwarové přerušení). Nebo využijte příklady, které jsou na úplném konci této kapitoly dole a budou Vám určitě fungovat :-). Většina asemblerského kódu Vám fungovat bude, kromě adresování přes ES:[], atd., které budete muset upravit. Pokud nejste tak daleko, že víte jak, raději použijte jednodušší řešení. Také musíte vědět, že pokud si instalujete funkci, kterou bude volat rutina z reálného módu, tak jí musíte na rutinu v chráněném módu nainstalovat CallBack. A my se zatím odebere k další kapitole, a tou je klávesnice (proč to dělat jednoduše, když to jde složitě).

KLÁVESNICE:

První, o co se pokusíme, bude prosté nahrazení jednotky CRT, která nám zprostředková dvě docela dobré funkce. Aniž bychom se dlouze rozepisovali, tak se jen zmíním, že klávesnice má své funkce na INT $16. Pokud se jimi rozhodneme nahradit CRT, bude nám stačit jen tento prostý kód (mohli bychom sice ještě použít INT 21h, ale proč vyhánět čerta ďáblem, že?):

function Readkey : word; assembler;
asm
	xor	ah,ah		{MOV	AL,0}
	int	16h
	or	al,al		{CMP	AL,0}
	je	@spec
	xor	ah,ah
	jmp	@konec
@spec:
	mov	al,ah
	mov	ah,1
@konec:
end;  

Tato funkce se chová podobně jako ReadKey s tím rozdílem, že jí není nutné volat 2x v případě, že nám vrátí 0. Tato funkce vrací jednoduše celý WORD. Pokud je stisknuta normální klávesa, vrátí nám její ASCII kód v dolním bytu. Pokud je to rozšířená klávesa, je její ASCII kód také v Lo bytu, ale v Hi je 1. Scan kódy se tedy nevrací (kdybyste je potřebovali, tak jsou v AH, pokud je AL<>0). Nahradit druhou nejpoužívanější funkci je stejně jednoduché:

function Keypressed : boolean; assembler;
asm
	xor	bl,bl		{mov	bl,false}
	mov	ah,1
	int	16h
	jz	@konec
	inc	bl		{mov	bl,true}
@konec:
	mov	al,bl
end;  

Nyní ale zkusíme něco, co nám CRT už nenabízí. Zjistíme stisknuté klávesy CTRL, ALT a SHIFT. Sice bychom na to mohli použít funkci z INT 16h (buď $12 nebo starší $2), ale mnohem rychlejší je načíst si to ručně. Vezme si na pomoc BIOS a ten nám říká, že na adrese 0:$417 je stav speciálních kláves nebo-li přeřaďovačů.

function SpecKlav : byte; assembler;
asm
	xor	ax,ax
	mov	es,ax
	mov	bx,417h
	mov	al,es:[bx]
	and	al,15
end;  

Tímto získáme stav kláves ALT, CTRL a levého či pravého SHIFTu. Pokud bychom chtěli získat všechny, jednoduše odmažeme poslední instrukci (prakticky, proč bychom ji vlastně měli vůbec používat, že?). Pak budou mít bity tento význam:

	0	pravý SHIFT
	1	levý SHIFT
	2	CTRL (levý nebo pravý)
	3	ALT (levý nebo pravý)
	4	Scroll Lock zapnut
	5	Num Lock zapnut
	6	Caps Lock zapnut
	7	Insert mód (přepisování / vkládání)

Pokud bychom funkci přepsali tak, aby vracela celý word, můžeme se dozvědět mnohem více informací, které jsou důležité:

function SpecKlav : word; assembler;
asm
	xor	ax,ax
	mov	es,ax
	mov	bx,417h
	mov	ax,es:[bx]
end;  

Význam prvních 8 bitů je stejný, přibyly však bity další (MSB):

	8	stisknut levý CTRL (pokud je zároveň bit 2 = 1)
	9	stisknut levý ALT (pokud zároveň bit 3 = 1)
	10	stisknut Print Screen (vyvolá zároveň INT 5)
	11	stav PAUSE (obdoba Insert módu)
	12	stisknuté tlačítko Scroll Lock (může být <> bitu 4)
	13	stisknuté tlačítko Num Lock (může být <> bitu 5)
	14	stisknuté tlačítko Caps Lock (může být <> bitu 6)
	15	stisknuté tlačítko Insert (může být <> bitu 7)  

Abychom mohli rychle testovat klávesy a nemusel znát jejich kódy, můžeme si je pojmenovat pomocí konstant:

const	Alt = 8;
	Shift = 1;
	Ctrl = 4;		{testujte SPECKLAV pomocí AND}
	PRShift   =   1;
	PLShift   =   2;
	PCtrl     =   4;
	PAlt      =   8;
	PScrLockM =  16;
	PNumLockM =  32;
	PCpsLockM =  64;
	PInsertM  = 128;
	PLCtrl    = 256;
	PLAlt     = 512;
	PPrintScr = 1024;
	PPauseM   = 2048;
	PScrLock  = 4096;
	PNumLock  = 8192;
	PCpsLock  = 16384;
	PInsert   = 32768;


	Enter = 13;		{testujte READKEY pomocí =}
	Esc = 27;
	Nahoru = 256+72;
	Dolu = 256+80;
	Doleva = 256+75;
	Doprava = 256+77;
	CtrlLevo = 256+115;
	CtrlPravo = 256+115;  

Můžeme si i napsat funkci, která přímo otestuje, zda je daný přepínač či speciální klávesa stisknuta či nikoliv:

function Kstav(Prep : word) : boolean; assembler;
asm
	xor	ax,ax
	mov	di,$417
	mov	es,ax
	mov	bx,es:[di]
	test	bx,prep
	jz	@ko
	inc	al
@ko:
end;  

A když jsme si takto urychlili jednu funkci, můžeme si urychlit i ty ostatní. Na to je ovšem potřeba znát trochu teorie.

Ovladač klávesnice ukládá postupně ASCII kódy kláves do tzv. fronty. Tato fronta je standardně velká 32 bytů. A standardně leží na adrese 0:$41e. Dvojice ASCII kód a Scan kód se ukládají cyklicky od začátku do konce (pokud klávesu držíte, posílá se její kód znovu po určité době, kterou si nastavujete ve Windows nebo v BIOSu; u některých novějších klávesnic přímo na nich). Pokud Vy přečtete z tohoto bufferu klávesu, čtete jí ze začátku fronty, který se tímto posune. Pokud Konec narazí na Začátek, je fronta plná a začne to pípat. Pokud je Konec = Začátek, je fronta prázdná. Protože se čte cyklicky, to, že jste dosáhli bytu 31 neznamená, že jste na konci fronty. Adresa klávesy, která se teď bude číst, je na adrese 0:$41a a má velikost 16 bitů. Jedná se vlastně o offset. Adresa, kam se bude ukládat příští klávesa je WORD na adrese 0:$41c. Ukazují standardně na jeden z těch 32 bytů. Protože adresa segment:offset se může napsat různými způsoby, můžeme vzít $40 jako segment a zbytek (tj. 0-$f) jako offset. Můžeme si to vysvětlit na následující funkci:

const	OffsKlav : word = $40;


function Keypressed : boolean; assembler;
asm
	xor	al,al		{klávesa není stisknutá; AL=0}
	mov	es,offsklav	{segment bufferu klávesnice}
	mov	bx,es:[$1a]	{přečteme začátek fronty}
	xor	bx,es:[$1c]	{porovnáme ho s koncem}
	or	bx,bx		{je výsledek 0, tj. rovnost?}
	je	@konec		{pokud ano, buffer je prázdný}
	inc	al		{jinak je tam nějaká klávesa}
@konec:
end;  

Funkce, která nám pak nahradí funkci ReadKey bude sice o trochu složitější, ale bude využívat mnohé z funkce Keypressed:

function ReadKey : word; assembler;
asm
	mov    es,offsklav
	mov    di,es:[$1a]
@cekej:
	cmp    di,es:[$1c]		{porovnáme adresu začátku a konce}
	je     @cekej			{je stejná? je = není klávesa, čekáme}
	mov    ax,es:[di]		{jinak danou klávesu načteme}
	add    word ptr es:[$1a],2	{musíme posunout začátek o 2 byty}
	cmp    word ptr es:[$1a],$3c	{překročili jsme maximální mez?}
	jbe    @zac
	mov    word ptr es:[$1a],$1e	{překročili, musíme nastavit na počátek}
@zac:					{ne, v pořádku}
end;  

Ovšem, BIOS pamatuje i na jisté výjimky. Pokud se podíváte na adresy 0:$480 a $482, zjistíte, že jsou zde údaje o počátku a konce bufferu pro klávesy. Standardně jsou zde hodnoty $1e a $3c, jakožto offsety od $400, avšak tvůrci zřejmě pamatovali i na aplikace, které potřebují frontu delší. Ty si ji např. mohou posunout až za (v ATHelpu 1.5 uvedený, ale je nutné počítat s tím, že dnes jsou v BIOSu uloženy další informace, např. co se týká otáček ventilátorů, správa napájení, atd.) 0:$4ff a vytvořit ji delší než 32 bytů. Pokud bychom měli jít do důsledků, měli bychom tyto údaje akceptovat, a pozměnit malinko funkci ReadKey (funkci Keypressed nemusíme, neboť ta není závislá na tom, kde je buffer):

var	Zacatek : word absolute $0:$480;
	Konec : word absolute $0:$482;


function ReadKey : word; assembler;
asm
	mov	es,offsklav
	mov	di,es:[$1a]
@cekej:
	cmp	di,es:[$1c]
	je	@cekej
	mov	ax,es:[di]
	add	word ptr es:[$1a],2
	mov	bx,konec
	cmp	word ptr es:[$1a],bx
	jbe	@zac
	mov	bx,zacatek
	mov	word ptr es:[$1a],bx
@zac:
end;  

Pokud se rozhodnete si buffer rozšířit sami, nezapomeňte nastavit adresy začátku a konce fronty (ne bufferu) tak, aby byly v rozsahu, který si definujete v Zacatek a Konec.

A budeme pokračovat. Pokud se budete pokoušet měnit stav přeřaďovačů (Num Lock, atd.), tak tím samozřejmě změníte chování programů, které jsou na nich závislé, ovšem na klávesnici budou diody svítit stále stejně. Jsou dvě řešení, jak je rozsvítit. Buď k tomu přinutíte DOS takovým trikem (význam se nesnažte pochopit):

procedure DOSled; assembler;
asm
	mov	ah,$b
	int	21h
end;  

Nebo se o to pokusíte sami. Klávesnice AT je totiž programovatelný mikropočítač 8042 (osekaný/předchůdce 8051) a programuje se přes porty PPI. Zapisovat můžete buď na port $64 nebo $60. Ten první je novější, ten druhý je jistější (kompatibilita). Na tento port však nemůžete nic jen tak zapsat. Musíte si nejprve počkat, až bude klávesnice připravena. Sekvence instrukcí pro vyslání příkazu je následující:

function PPI(Prikaz,Data : byte) : boolean; assembler;
asm
	Xor	cx,cx
@pockej:
	in	al,64h
	test	al,2
	jz	@posli
	loop	@pockej
	jmp	@chyba
@posli:
	mov	al,prikaz
	out	64h,al
	mov	cx,2000h
@cekej:
	loop	@cekej
	mov	al,data
	out	64h,al
	mov	al,1
	ret
@chyba:	xor	al,al	{vypršel limit, klávesnice nedopovídá}
end;  

Seznam všech příkazů je uveden v ATHelpu, nás bude zajímat příkaz $ed, kterým se rozsvícejí diody. Data pro tento příkaz složíte následovně:

	xor	ax,ax
	mov	es,ax
	mov	ax,es:[$417]
	and	ax,$7f
	shr	ax,4
	mov	data,ax  

Tím bychom mohli skončit, ale znáte mne. Když už jsme se pokusili udělat si vlastní ovladač myši, proč ne vlastní ovladač klávesnice. Je známo, že klávesnice obsazuje přerušení INT 9h. Pokud se na něj pověsíme, vyřadíme tím defakto původní obsluhu. Proč bychom to chtěli dělat? Např. si chceme zřídit vlastní frontu. A abychom to nekomplikovali, nebudeme do ní ukládat ASCII kódy, ale Scan kódy. Proč? Pro každý z nich totiž bude stačit jen 1 byte. Dejme tomu, že si napíšeme rutinu se jménem KEYBOARD. Nainstalujeme a odinstalujeme ji obvyklým způsobem:

const	MaxKodu = 512;


var	Int9Save : pointer;
	Buffer : array[0..MaxKodu-1] of byte;
	BufS,BufK : word;


procedure InstallKeyb;
begin
 GetIntVec(9,Int9Save);
 SetIntVec(9,@Keyboard);
 BufS := 0;
 BufK := 0;
end;


pocedure UnInstallKeyb;
begin
 SetIntVec(9,Int9Save);
end;  

A nyní začneme psát naši rutinu, a to doslova :-). A musíme vyřešit jeden zajímavý problém. Jistě si pamatujete, že pokud je start a konec fronty na té samé adrese, je fronta prázdná. Jak ale definovat, že je fronta plná? Uděláme to např. tak, že 1 byte ve frontě zůstane nevyužitý a tedy bude platit, že pokud S=0 a K=MAXKODU-1, nebo K=S-1, tak je fronta plná a už nic zapisovat nebudeme (trochu si užijeme assembleru. Nevěřte tomu, že to je málo výkonný nástroj. Má sice málo instrukcí, ale mixér a počítač se také zapínají jedním a tím samým vypínačem, že? A přitom je mezi nimi dost velký rozdíl).

{$F+}
procedure Keyboard; interrupt;  assembler;
asm
	mov	ax,bufk
	mov	bx,bufs
	or	bx,bx
	jne	@startneni0
	or	ax,ax		{fronta je prázdná}
	je	@muzemepsat
	mov	cx,maxkodu
	dec	cx
	cmp	ax,cx
	je	@konec		{start = 0 a konec = max-1, fronta je plná}
	jmp	@muzemepsat
@startneni0:
	dec	bx
	xor	bx,ax		{je konec = start - 1}
	jz	@konec		{ano, fronta je plná, nic ukládat nebudeme}
@muzemepsat:
	in	al,$60		{načteme Scan kód klávesy}
				{zjistíme adresu bufferu}
	mov	di,offset buffer
				{LES nejde, protože to není adresa v zásobníku}
				{segment se uloží do odpovídajícího registru}
				{buď DS nebo ES, na 99.9% ten druhý :-)}
	add	di,bufk		{přičteme k ní konec}
	mov	[di],al
	mov	ax,bufk		{jdeme na další pozici}
	inc	ax
	cmp	ax,maxkodu	{jsme na konci bufferu?}
	jne	@ok
	xor	ax,ax
@ok:	mov	bufk,ax		{zapíšeme konec zpět}
@konec:				{teď musíme zajistit správnou funkci HW přerušení}
	in	al,61H		{zjistíme původní hodnotu}
	mov	ah,al
	or	al,80h		{povolíme klávesnici}
	out	61H,al
	xchg	ah,al		{a zapíšeme opět původní hodnotu}
	out	61H,al
	mov	al,$20		{potvrdíme zpracování přerušení}
	out	$20,al
end;
{$F-}  

Namísto všeho, co je za slovem KONEC můžeme napsat jen prosté:

jmp far int9save

Pokud chcete volat ještě původní obsluhu, i když nevím, k čemu to bude dobré zrovna v tomto případě (vyřadíte tak spoustu ostatních TRS programů, které čekají na konkrétní klávesu, což se může někdy hodit).

Výsledný kód ve Free Pascalu bude vypadat následovně:

procedure Keyboard; interrupt; far; assembler;
asm
	mov	ax,bufk
	mov	bx,bufs
	or	bx,bx
	jne	@startneni0
	or	ax,ax
	je	@muzemepsat
	mov	cx,maxkodu
	dec	cx
	cmp	ax,cx
	je	@konec
	jmp	@muzemepsat
@startneni0:
	dec	bx
	xor	bx,ax
	jz	@konec
@muzemepsat:
	in	al,$60
	mov	edi,buffer
	add	edi,bufk
	mov	[edi],al	{pozor! ADD DI projde, ale [DI] už ne!}
	mov	ax,bufk
	inc	ax
	cmp	ax,maxkodu
	jne	@ok
	xor	ax,ax
@ok:	mov	bufk,ax
@konec:
	in	al,61H
	mov	ah,al
	or	al,80h
	out	61H,al
	xchg	ah,al
	out	61H,al
	mov	al,$20
	out	$20,al
end;
procedure int9_dummy; begin end;


procedure InstallKeyb;
begin				{HW(!) přerušení 0-15 musí mít zamčená data a kód!}
 lock_code(@keyboard,longint(@int9_dummy)-longint(@keyboard));
 lock_data(buffer,sizeof(buffer));
 lock_data(BufS,sizeof(BufS)+SizeOf(BufK));
 get_pm_interrupt(9,Int9Save);	{$1C nemusí...}
 set_pm_interrupt(9,@Keyboard);
 BufS := 0;
 BufK := 0;
end;


pocedure UnInstallKeyb;
begin
 set_pm_interrupt(9,Int9Save);
 unlock_code(@keyboard,longint(@int9_dummy)-longint(@keyboard));
 unlock_data(buffer,sizeof(buffer));
 unlock_data(BufS,sizeof(BufS)+SizeOf(BufK));
end;  

Rutina čte všechny kódy a ukládá je do bufferu. Je nutno ovšem podotknout, že některé novější klávesy posílají tzv. rozšířené kódy, které začínají kódem $e0 nebo $e1 a po nich následuje několik dalších kódů (po $e0 vždy 1, po $e1 vždy 2; navíc se $e1 opakuje ve dvojicích na jednu klávesu a pokud je za $e0 kód $aa nebo $46, následuje také další $e0 a ještě jeden kód). Standardně je teď načítáme do bufferu. Tam si je musí přebrat už čtecí rutina. Jednodušší by bylo tyto sekvence kódů převádět na 1 bytové kódy, které ještě nejsou obsazené, tj. od $59 nahoru (včetně), tzn. že musíte mít nějaký pracovní buffer o délce pár bytů (ne u procedury, ale v hlavním programu), kam si budete skládat ty delší kódy a až proběhnou všechny, tak z toho složit 1 byte a teprve ten zapsat do hlavního bufferu. Čtecí funkce pak mohou vypadat takto:

function KeyPressed : boolean;
begin
 Keypressed := BufK <> BufS;
end;


function ReadKey : byte;
begin
 if BufK = BufS then while not Keypressed do;
 Readkey := Buffer[BufS];
 BufS := (BufS+1) and 511;	{protože vel.buf.je mocnina dvou, můžeme namísto MOD 512}
end;				{použít tento trik, abychom udrželi BufS v mezích}  

Použitelné scan kódy jsou opět v ATHelpu. Jmenujme alespoň několik těch nejzajímavějších:

	1		ESC
	2-11		1 až 0
	28		Enter
	57		Mezerník  

Písmena jsou skenována postupně po řádcích, takže nejdou podle abecedy. Můžete si napsat funkci, která bude využívat tabulku, pro převod Scan kódu na ASCII znaky (vytvořil jsem jen prvních několik znaků). Zadaný scan kód nesmí být 0!

function ASCII(Scan : byte) : char;
const znaku = 8;
      pole : array[1..znaku] of char = (#27,'1','2','3','4','5','6','7')
begin
 if Scan <= znaku then
  ASCII := pole[scan];
end;  

A to je všechno. Pokud ještě vůbec něco chápete, tak klobouk dolů. Já po těch 6 hodinách psaní už nechápu nic :-). Kdybyste našli chybu nebo chtěli něco doplnit, klidně mi (nebo spíše adminovi) napište. Pokud by se Vám zdálo, že je to nepřehledné, složité, nebo málo vysvětlené, můžete zavítat na http://www.programujte.com/, kde se čirou náhodou (když to člověk 5 let hledá, nenajde nic, ale jakmile o tom začne psát, tak zjistí, že už to jako napotvoru existuje) píše také o použití myši, grafiky, XMS a PCX (budeme brát příště). Něco z toho se dozvíte jen tady, něco jen tam, spousta je toho společného, i když řešeno různými způsoby (počítal bych na 99%, že alespoň ta ne-moje verze bude určitě fungovat :-D), takže určitě nic nezkazíte, když si pročtete obojí. A na závěr jako bonus mám použití myši a klávesnice ve Free Pascalu velice jednoduchou metodou...

Free Pascal pro Windows a Linux:

Dva příklady pro využití myši a klávesnice ve všech systémech, které Free Pascal podporuje, jsem vybral z jeho dokumentace. Můžete je použít jak v DOSu, tak pro Win32 či Linux aplikace. Pro čtení joysticku budete muset buď použít DirectInput jednotku nebo najít nějakou jinou, která už přímo čtení Joysticku (a všech ostatních MIDI analogových (ta můžete připojovat a odpojovat i za chodu počítače) zařízení, jako jsou volanty s pedály či letecké páky) podporuje. Jedná se pouze o základní použití. Zvláště u myši najdete jistě i jiná použití, např. s využitím funkcí ShowMouse a HideMouse (viz. nápověda FP).

Myš funguje podobně jako v našich příkladech. Nejprve je inicializujeme, čímž získáme také počet aktivních tlačítek. Rozdíl oproti nám je ten, že musíme zase zrušit ovladač před ukončením programu (i když i v našich případech bylo nutné použít něco jako deaktivaci v případě, že jste měnili citlivost myši či instalovali svůj handler přerušení). Po správné detekci myši už jen čteme souřadnice a vypisujeme je, popř. čekáme na stisk pravého tlačítka myši, které zde testujeme s pomocí předdefinované konstanty. Pokud budeme chtít vidět i daný kurzor, musíme jej zobrazit pomocí ShowMouse. Pokud budete ale něco kreslit na obrazovku (např. texty), musíte před jejich kreslením provést HideMouse, pak to vypsat a poté zase ShowMouse, jinak se může stát, že v případě, kdy bude kurzor myši nad místem, kam se chystáte psát, bude po zápisu a pohnutí myši na jeho místě původní znak a ne ten, který jste zapsali nyní.

Uses mouse;
Var Buttons : Byte;
begin
 InitMouse;
 Buttons := DetectMouse;
 if Buttons = 0 then
  Writeln('Myš není připojena.')
 else
  Writeln('Nalezena myš se ',Buttons,' tlačítky.');
 repeat
  X := GetMouseX;
  Y := GetMouseY;
  Writeln('X,Y= (',X,',',Y,')');
 until GetMouseButtons = MouseRightButton;
 DoneMouse;
end.  

Při použití jednotky KEYBOARD je nutné používat tzv. události. Navíc je potřeba klávesnici stejně jako myš nejprve inicializovat a před skončením programu zase deaktivovat. Vždy musíte získat událost a teprve tu převést na klávesu. Je to sice trochu neohrabané a možná i pomalé, ale může to posloužit jako dobrý příklad. Až získáte znalosti, můžete si stáhnout zdrojový kód jednotky Keyboard a vložit si do svého kódu jen ty funkce, které potřebujete a případně je patřičně upravit.

uses keyboard;
Var K : TKeyEvent;
begin
 InitKeyBoard;
 Writeln('Tiskněte klávesy, stiskněte "q" pro ukončení.');
 repeat
  K := GetKeyEvent;
  K := TranslateKeyEvent(K);
  Write('Ziskana klavesa s touto udalosti: ');
  case GetKeyEventFlags(K) of
    kbASCII    : Writeln('ASCII klávesa');
    kbUniCode  : Writeln('Unicode klávesa');
    kbFnKey    : Writeln('Funkční klávesa');
    kbPhys     : Writeln('Somatická klávesa');
    kbReleased : Writeln('Uvolnění klávesy');
  end;
  K := TranslateKeyEvent(K);
  Writeln('Získaná klávesa : ',KeyEventToString(K));
 until GetKeyEventChar(K) = 'q';
 DoneKeyBoard;
end.  

A to je pro dnešek opravdu vše. Příště se podíváme na seriál o grafice, kde se naučíte načítat obrázky BMP, inicializovat různé režimy, používat XMS paměť. A později k tomu přidáme i zvuky WAVe na zvukových kartách.

2006-12-20 | Martin Lux
Reklamy: