   #Start Contents English version

                  Bezposredni dostep do ekranu pod Linuksem

   Jesli chodzi o wyswietlanie informacji na ekranie, nie jestesmy
   ograniczeni tylko do pisania w miejscu, gdzie akurat znajduje sie
   kursor. Na pewno widzieliscie jakis program, ktory mimo iz mial
   tekstowy interfejs, to jednak pisal po ekranie, gdzie mu sie podobalo.
   Tym wlasnie sie teraz zajmiemy.
     _________________________________________________________________

Pisanie z wykorzystaniem sekwencji kontrolnych terminala

   (przeskocz sekwencje kontrolne)

   Kazdy program terminala ma inne sekwencje kontrolne i jesli chcecie
   pisac programy, ktore beda dzialac na kazdym terminalu, zainteresujcie
   sie biblioteka ncurses. Tutaj opisze tylko kilka sekwencji
   standardowego terminala xterm.

   Pierwsza sprawa: co to wlasciwie jest znak kontrolny (sekwencja
   kontrolna)?
   Jest to specjalny ciag znakow okreslajacych zachowanie sie terminala.
   Kilka juz na pewno znacie: BEL (dzwiek), CR/LF (przechodzenie do nowej
   linii), TAB (tabulator). Teraz dojda jeszcze dwa: zmiana koloru tekstu
   i tla oraz przechodzenie do wyznaczonej pozycji na ekranie.

   Korzystalem z pliku xterm_controls.txt. Mozecie skorzystac takze z
   tego pliku lub z informacji na stronie podrecznika - man 4
   console_codes.

Kolorowanie tekstu

   (przeskocz kolorowanie)

   Sekwencja kontrolna odpowiedzialna za kolor tekstu i tla wyglada tak:
   ESC[(atr);(lit);(tlo)m,
   gdzie:
     * ESC to kod klawisza Escape, czyli 1Bh. Jesli ktos jest ciekawy, to
       z klawiatury otrzymuje sie ten znak wciskajac Ctrl+V, po czym ESC.
     * (atr) = atrybut znaku. Jest to jedna z ponizszych wartosci (jako
       tekst, nie binarnie):
          + 0 - przywroc wszystkie atrybuty (powrot do trybu normalnego)
          + 1 - jasny (zwykle wlacza tez wytluszczona czcionke)
          + 2 - przyciemnienie znaku
          + 3 - podkreslenie
          + 5 - mruganie znaku
          + 7 - odwrocenie kolorow
          + 8 - ukryty
     * (lit) = kolor litery:
          + 30 - czarny
          + 31 - czerwony
          + 32 - zielony
          + 33 - zolty
          + 34 - niebieski
          + 35 - magenta (rozowy)
          + 36 - cyan (blekitny)
          + 37 - bialy
     * (tlo) = kolor tla:
          + 40 - czarny
          + 41 - czerwony
          + 42 - zielony
          + 43 - zolty
          + 44 - niebieski
          + 45 - magenta (rozowy)
          + 46 - cyan (blekitny)
          + 47 - bialy
     * m - doslownie: mala litera m

   Na przyklad, aby napisac cos na czerwono i przywrocic oryginalne
   kolory konsoli, nalezy normalnie (czyli przy uzyciu int 80h z EAX=4,
   EBX=1, ECX=adres, EDX=dlugosc) wyswietlic taki oto ciag znakow:

   1bh, "[0;31;40m Napis", 1bh, "[0;37;40m".

   Ten ostatni ciag przywraca domyslne kolory terminala (szarobialy na
   czarnym tle). Jesli uzywacie terminala uzywajacego innego zestawu
   kolorow niz szarobialy na czarnym tle, mozecie wstawic wlasne
   wartosci, tak samo jak dla zwyklych napisow - terminal zapamieta
   ustawienia. Mozecie tez sprobowac takiej sekwencji:

   1bh, "[0;31;40m Napis", 1bh, "[0;39;49m".

   Wartosci 39 i 49 przywracaja domyslne kolory, odpowiednio dla znakow i
   tla.
   Mozna tez sprobowac przywrocenia domyslnych wartosci wszystkich
   atrybutow (nie tylko kolorow) bez ustawiania nowych wartosci:

   1bh, "[0;31;40m Napis", 1bh, "[0m".

Zmiana biezacej pozycji kursora

   (przeskocz teorie)

   Sekwencja kontrolna odpowiedzialna za ustalanie pozycji kursora
   wyglada tak:
   ESC [ w ; k H,
   gdzie:
     * ESC to kod klawisza Escape, czyli 1Bh.
     * w = numer wiersza (jesli nie podano, przyjmuje sie 1)
     * k = numer kolumny (jesli nie podano, przyjmuje sie 1)
     * H - doslownie: litera wielkie H

   Na przyklad, jesli chcemy cos napisac w dziesiatym wierszu dziesiatej
   kolumny, nalezy normalnie (czyli przy uzyciu int 80h z EAX=4, EBX=1,
   ECX=adres, EDX=dlugosc) wyswietlic ciag znakow:

   1bh, "[10;10HNapis"
     _________________________________________________________________

   A oto obiecany program do rysowania ramek:
   (przeskocz program)
; Rysowanie okienek z ramka
;
; Autor: Bogdan D.
;
; nasm -O999 -o ramki.o -f elf ramki.asm
; ld -s -o ramki ramki.o

section .text
global _start


_start:
        mov     eax, 4
        mov     ebx, 1
        mov     ecx, czysc
        mov     edx, czysc_dl
        int     80h                     ; wyswietlamy sekwencje,
                                        ; ktora wyczysci ekran

        mov     ax, (36<<8)+44          ; kolor znakow, kolor tla:
                                        ; zolty na niebieskim
        mov     bx, 1                   ; kolumna Lewa-Gorna (L-G)
        mov     cx, 1                   ; wiersz L-G
        mov     si, 9                   ; kolumna Prawa-Dolna (P-D)
        mov     bp, 9                   ; wiersz P-D
        call    rysuj_okienko

        mov     ax, (37<<8)+40          ; bialy na czarnym
        mov     bx, 10
        mov     cx, 10
        mov     si, 20
        mov     bp, 16
        call    rysuj_okienko

        mov     eax, 4
        mov     ebx, 1
        mov     ecx, nwln
        mov     edx, 1
        int     80h                     ; wyswietlamy znak przejscia
                                        ; do nowej linii

        mov     eax, 1
        xor     ebx, ebx
        int     80h                     ; wyjscie z programu


rysuj_okienko:

; wejscie:
;
;  AH = atrybut znaku (kolor)
;  AL = kolor tla
;  BX = kolumna lewego gornego rogu
;  CX = wiersz lewego gornego rogu
;  SI = kolumna prawego dolnego rogu
;  BP = wiersz prawego dolnego rogu
;
; wyjscie:
;  nic

; podwojne ramki ASCII
;r_p    equ     0bah                    ; prawa boczna
;r_pg   equ     0bbh                    ; prawa gorna (naroznik)
;r_pd   equ     0bch                    ; prawa dolna

;r_g    equ     0cdh                    ; gorna
;r_d    equ     r_g                     ; dolna

;r_l    equ     r_p                     ; lewa boczna
;r_lg   equ     0c9h                    ; lewa gorna
;r_ld   equ     0c8h                    ; lewa dolna


r_p     equ     "|"                     ; prawa boczna
r_pg    equ     "\"                     ; prawa gorna (naroznik)
r_pd    equ     "/"                     ; prawa dolna

r_g     equ     "="                     ; gorna
r_d     equ     r_g                     ; dolna

r_l     equ     r_p                     ; lewa boczna
r_lg    equ     "/"                     ; lewa gorna
r_ld    equ     "\"                     ; lewa dolna

spacja  equ     20h


        push    bx
        push    cx

        mov     dl, r_lg
        call    znak                    ; rysujemy lewy gorny naroznik

        push    bx
        mov     dl, r_g                 ; bedziemy rysowac gorna krawedz

                                        ;dopoki BX<SI, rysuj gorna krawedz
.rysuj_gora:
        inc     bx
        cmp     bx, si
        je      .dalej
        call    znak
        jmp     short .rysuj_gora

.dalej:
        mov     dl, r_pg
        call    znak                    ; rysujemy prawy gorny naroznik
        pop     bx
        push    bx

                                        ; rysujemy srodek
                                        ;dopoki CX<BP, rysuj wnetrze ramki
.rysuj_srodek:
        inc     cx
        cmp     cx, bp
        je      .ostatni

        mov     dl, r_l
        call    znak                    ; zaczynamy od lewego brzegu ramki

        push    bx
        mov     dl, spacja              ; w srodku beda spacje
.rysuj_srodek2:
        inc     bx
        cmp     bx, si          ; dopoki BX<SI, rysuj wnetrze (spacje)
        je      .dalej2
        call    znak
        jmp     short .rysuj_srodek2

.dalej2:
        mov     dl, r_p
        call    znak                    ; rysujemy prawy brzeg
        pop     bx

        jmp     short .rysuj_srodek

.ostatni:
        mov     dl, r_ld
        call    znak                    ; rysujemy lewy dolny naroznik
        pop     bx

        mov     dl, r_d         ; bedziemy rysowac dolna krawedz ramki
.rysuj_dol:
        inc     bx
        cmp     bx, si                  ;dopoki BX<SI, rysuj dolna krawedz
        je      .dalej3
        call    znak
        jmp     short .rysuj_dol

.dalej3:
        mov     dl, r_pd
        call    znak                    ; rysujemy prawy dolny naroznik

        pop     cx
        pop     bx

        ret


znak:

;  AH = atrybut znaku (kolor)
;  AL = kolor tla
;  BX = kolumna znaku
;  CX = wiersz znaku
;  DL = znak

        push    eax
        push    ebx
        push    ecx
        push    edx

        push    ax
        mov     dh, 10
        shr     ax, 8                   ; AX = kolor znaku
        div     dh                      ; AL = AL/10, AH = AL mod 10
        add     ax, "00"                ; do ilorazu i reszty dodajemy
                                        ; kod ASCII cyfry zero
        mov     [fg], ax                ; do [fg] zapisujemy numer
                                        ; koloru znaku

        pop     ax
        and     ax, 0FFh                ; AX = kolor tla
        div     dh                      ; dzielimy przez 10
        add     ax, "00"
        mov     [bg], ax

        mov     ax, bx                  ; AX = kolumna znaku
        and     ax, 0FFh
        div     dh                      ; dzielimy przez 10
        add     ax, "00"
        mov     [kolumna], ax

        mov     ax, cx                  ; AX = wiersz znaku
        and     ax, 0FFh
        div     dh                      ; dzielimy przez 10
        add     ax, "00"
        mov     [wiersz], ax

        mov     [znaczek], dl           ; zapisujemy, jaki znak
                                        ; mamy wyswietlic


        mov     eax, 4
        mov     ebx, 1
        mov     ecx, pozycja
        mov     edx, napis_dl
        int     80h                     ; wyswietlamy napis wraz z
                                        ; przejsciem na odpowiednia pozycje

        pop     edx
        pop     ecx
        pop     ebx
        pop     eax

        ret


section .data

ESC             equ     1Bh

pozycja         db      ESC, "["        ; sekwencja zmiany pozycji kursora
wiersz          db      "00;"
kolumna         db      "00H"
napis           db      ESC, "["        ; sekwencja zmiany koloru
atr             db      "0;"
fg              db      "00;"
bg              db      "00m"
znaczek         db      "x"             ; znak, ktory bedziemy wyswietlac
napis_dl        equ     $ - pozycja

czysc           db      ESC, "[2J"      ; sekwencja czyszczaca caly ekran
czysc_dl        equ     $ - czysc

nwln            db      10
     _________________________________________________________________

Pisanie z wykorzystaniem urzadzen znakowych /dev/vcsaN

   (przeskocz vcsa)

   Innym sposobem na poruszanie sie po ekranie jest zapis do specjalnych
   urzadzen znakowych - plikow /dev/vcsaN (mozliwe, ze potrzebne beda
   uprawnienia roota).

   Na stronach podrecznika man vcsa (a konkretnie to w przykladowym
   programie) widac, ze format tych plikow jest dosc prosty - na poczatku
   sa 4 bajty, odpowiadajace: liczbie wierszy, liczbie kolumn (bo
   przeciez moga byc rozne rozdzielczosci) oraz pozycji x i y kursora.
   Potem ida kolejno znaki widoczne na ekranie (od lewego gornego rogu
   wzdluz wierszy) i ich atrybuty. Atrybuty te sa takie same, jak w
   kursie dla DOSa i podobnie jak tam, starsze 4 bity oznaczaja kolor
   tla, a mlodsze - kolor znaku.

   Teraz widzicie, ze to nic trudnego - wystarczy otworzyc plik, odczytac
   wymiary ekranu i zapisywac odpowiednie bajty na odpowiednich pozycjach
   (uzywajac funkcji poruszania sie po pliku lub, po zmapowaniu pliku do
   pamieci, po prostu pisac po pamieci).

   Oto przykladowy program:
   (przeskocz program z vcsa)
; Program bezposrednio zapisujacy do pliku konsoli
;
; Autor: Bogdan D., bogdandr MALPKA op KROPKA pl
;
; kompilacja:
;
; nasm -O999 -f elf -o konsola.o konsola.asm
; ld -s -o konsola konsola.o


%idefine        sys_exit                1
%idefine        sys_read                3
%idefine        sys_write               4
%idefine        sys_open                5
%idefine        sys_close               6
%idefine        sys_lseek               19
%define         SEEK_SET                0
%define         O_RDWR                  02o

; pozycja, pod ktora coc wyswietlimy
%define         nasz_wiersz             10
%define         nasza_kolumna           10

section .text

global _start

_start:
        mov     eax, sys_open           ; otwieranie pliku
        mov     ebx, plik               ; nazwa pliku
        mov     ecx, O_RDWR             ; odczyt i zapis
        mov     edx, 600q               ; odczyt i zapis dla uzytkownika
        int     80h                     ; otwieramy plik

        cmp     eax, 0
        jl      .koniec

        mov     ebx, eax                ; uchwyt do pliku

        mov     eax, sys_read           ; czytanie z pliku (najpierw
                                        ; atrybuty konsoli)
        mov     ecx, konsola            ; dokad czytac
        mov     edx, 4                  ; ile czytac
        int     80h


        mov     eax, sys_lseek          ; przejscie na wlasciwa pozycje

        movzx   ecx, byte [l_kolumn]
        imul    ecx, nasz_wiersz
        add     ecx, nasza_kolumna      ;ECX=wiersz*dlugosc wiersza+kolumna

        shl     ecx, 1                  ; ECX *= 2, bo na ekranie sa: bajt
                                        ; znaku i bajt atrybutu
        add     ecx, 4                  ; +4, bo bedziemy szli
                                        ; od poczatku pliku

        mov     edx, SEEK_SET           ; od poczatku pliku
        int     80h

        mov     eax, sys_write          ; pisanie do pliku
        mov     ecx, znak               ; co zapisac
        mov     edx, 2                  ; ile zapisac
        int     80h

        mov     eax, sys_close          ; zamkniecie pliku
        int     80h

        xor     eax, eax                ; EAX = 0 = bez bledu

.koniec:
        mov     ebx, eax
        mov     eax, sys_exit
        int     80h                     ;wyjscie z kodem zero lub z bledem,
                                        ; ktory byl przy otwarciu pliku


section .data

plik    db      "/dev/vcsa1", 0 ; plik pierwszej konsoli tekstowej

                                        ; atrybuty czytanej konsoli:
konsola:
l_wierszy       db      0
l_kolumn        db      0
kursor_x        db      0
kursor_y        db      0

                                ; znak z atrybutem, ktory wyswietlimy:
znak            db      "*"
atrybut         db      43h             ; blekit na czerwonym
     _________________________________________________________________

Pisanie z wykorzystaniem mapowania pamieci

   (przeskocz mapowanie pamieci)

   Jeszcze jednym sposobem na pisanie po ekranie jest zapisywanie
   bezposrednio do pamieci trybu tekstowego. Pamiec ta znajduje sie w
   segmencie B800, co odpowiada liniowemu adresowi B8000, liczac od
   adresu 0. Oczywiscie system, ze wzgledow bezpieczenstwa, nie pozwoli
   nam bezposrednio pisac pod ten adres, wiec musimy sobie poradzic w
   inny sposob. Sposob ten polega na otwarciu specjalnego pliku
   urzadzenia, ktory symbolizuje cala pamiec w komputerze - /dev/mem. Na
   wiekszosci systemow otwarcie tego pliku wymaga uprawnien
   administratora.

   Po otwarciu pliku mamy dwie mozliwosci. Pierwsza to poruszac sie po
   nim funkcjami do zmiany pozycji w pliku, oraz odczytywac i zapisywac
   funkcjami odczytu i zapisu danych z i do pliku. Moze to byc powolne,
   ale sposob jest. Druga mozliwosc to zmapowac plik do pamieci, po czym
   korzystac z niego jak ze zwyklej tablicy. Te mozliwosc opisze teraz
   szczegolowo.

   Otwieranie pliku odbywa sie za pomoca tradycyjnego wywolania:
        mov     eax, 5          ; sys_open
        mov     ebx, pamiec     ; adres nazwy pliku "/dev/mem", 0
        mov     ecx, 2          ; O_RDWR, zapis i odczyt
        mov     edx, 666o       ; pelne prawa
        int     80h
        ...
        pamiec          db      "/dev/mem", 0

   Drugim krokiem jest zmapowanie naszego otwartego pliku do pamieci.
   Odbywa sie to za pomoca funkcji systemowej sys_mmap2. Przyjmuje ona 6
   argumentow:
    1. EBX = adres, pod jaki chcielibysmy zmapowac plik. Najlepiej podac
       zero, wtedy system sam wybierze dogodny adres
    2. ECX = dlugosc mapowanego obszaru pliku, w bajtach. Podamy to
       100000h, by na pewno objac obszar zaczynajacy sie B8000 i dlugosci
       4000 bajtow (tyle, ile trzeba na jeden ekran w trybie tekstowym,
       na znaki i ich atrybuty)
    3. EDX = tryb dostepu do zmapowanej pamieci. Jesli chcemy odczyt i
       zapis, podamy tutaj PROT_READ=1 + PROT_WRITE=2
    4. ESI = tryb wspoldzielenia zmapowanej pamieci. Podamy tu
       MAP_SHARED=1 (wspoldzielona, nie prywatna)
    5. EDI = deskryptor otwartego pliku, ktory chcemy zmapowac
    6. EBP = adres poczatkowy w pliku, od ktorego mapowac. Adres ten jest
       podawany w jednostkach strony systemowej, ktorej wielkosc moze byc
       rozna na roznych systemach. Najlatwiej podac tu zero, a do adresow
       dodawac potem B8000

   Po pomyslnym wykonaniu, system zwroci nam w EAX adres zmapowanego
   obszaru pamieci, ktorego mozemy uzywac (w przypadku bledu otrzymujemy
   wartosc od -4096 do -1 wlacznie). Przykladowe wywolanie wyglada wiec
   tak:
        mov     eax, 192                ; sys_mmap2
        xor     ebx, ebx                ; jadro wybierze adres
        mov     ecx, 100000h            ; dlugosc mapowanego obszaru
        mov     edx, 3                  ; PROT_READ | PROT_WRITE, mozliwosc
                                        ; zapisu i odczytu
        mov     esi, 1                  ; MAP_SHARED - tryb wspoldzielenia
        mov     edi, [deskryptor]       ; deskryptor pliku pamieci, otrzymany
                                        ; z sys_open w poprzednim kroku
        mov     ebp, 0                  ; adres poczatkowy w pliku
        int     80h

   Teraz wystarczy juz korzystac z otrzymanego wskaznika, na przyklad:
        mov     byte [eax+0b8000h], 'A'

   Ekran w trybie tekstowym sklada sie z 80*25=2000 znakow, a kazdy z
   nich ma po sobie bajt argumentu, mowiacy o kolorze znaku i tla:

   b8000 - znak 1, w lewym gornym rogu
   b8001 - atrybut znaku 1
   b8002 - znak 2, znajdujacy sie o 1 pozycje w prawo od znaku 1
   b8003 - atrybut znaku 2
   i tak dalej

   Czym zas jest atrybut?
   Jest to bajt mowiacy o kolorze danego znaku i kolorze tla dla tego
   znaku. Bity w tym bajcie oznaczaja:
     * 3-0 - kolor znaku (16 mozliwosci)
     * 6-4 - kolor tla (8 mozliwosci)
     * 7 - miganie znaku (jesli nie dziala, to oznacza, ze mamy 16
       kolorow tla zamiast 8)

   Wartosci kolorow:
   Czarny - 0, niebieski - 1, zielony - 2, blekitny - 3, czerwony - 4,
   rozowy - 5, brazowy - 6, jasnoszary (ten standardowy) - 7, ciemnoszary
   - 8, jasnoniebieski - 9, jasnozielony - 10, jasnoblekitny - 11,
   jasnoczerwony - 12, jasnorozowy - 13, zolty - 14, bialy - 15.

   Zmiany, ktore zapiszemy w pamieci, moga jednak nie od razu pojawic sie
   w pliku (czyli na ekranie w tym przypadku). Aby wymusic fizyczny zapis
   danych, korzysta sie z funkcji sys_msync. Przyjmuje ona 3 argumenty:
    1. EBX = adres poczatku danych do synchronizacji
    2. ECX = liczba bajtow do zsynchronizowania
    3. EDX = 0 lub zORowane flagi: MS_ASYNC=1 (wykonaj asynchronicznie),
       MS_INVALIDATE=2 (uniewaznij obszar po zapisaniu), MS_SYNC (wykonaj
       synchronicznie)

   Przykladowe wywolanie wyglada wiec tak:
        mov     eax, 144                ; sys_msync
        mov     ebx, 0b8000h            ; adres startowy
        mov     ecx, 4000               ; ile zsynchronizowac
        mov     edx, 0                  ; flagi
        int     80h

   Po zakonczeniu pracy z plikiem, mozemy go odmapowac:
        mov     eax, 91                 ; sys_munmap
        mov     ebx, [wskaznik]         ; wskaznik otrzymany z sys_mmap2
        mov     ecx, 100000h            ; liczba bajtow
        int     80h

   i zamknac:
        mov     eax, 6                  ; sys_close
        mov     ebx, [deskryptor]       ; deskryptor pliku "/dev/mem"
        int     80h

   Jak widac, mapowanie plikow do pamieci jest wygodne, gdyz nie trzeba
   ciagle skakac po pliku funkcja sys_lseek i wykonywac kosztownych
   czasowo wywolan innych funkcji systemowych. Warto wiec sie z tym
   zaznajomic. Nalezy jednak pamietac, ze nie wszystkie pliki czy
   urzadzenia daja sie zmapowac do pamieci - nie nalezy wtedy zamykac
   swojego programu z bledem, lecz korzystac z tradycyjnego interfejsu
   funkcji plikowych.

   Spis tresci off-line (klawisz dostepu 1)
   Spis tresci on-line (klawisz dostepu 2)
   Ulatwienia dla niepelnosprawnych (klawisz dostepu 0)
