   #Start Contents

                       Programowanie myszy pod Linuksem

   Wbudowanie obslugi myszy do swoich programow wcale nie musi byc
   trudne. Nie musimy bawic sie w pisanie sterownikow do kazdej mozliwej
   myszki ani bezposrednio "rozmawiac" ze sprzetem. Cala ta robota
   zostala juz za nas zrobiona. Komunikacja z mysza zajmie sie serwer
   myszy GPM, a my zajmiemy sie tylko uruchamianiem odpowiednich funkcji,
   jakie ten serwer oferuje.

   Podane tu przeze mnie informacje pochodza z moich wlasnych
   interpretacji pliku /usr/include/gpm.h oraz ze swietnego artykulu
   linuxjournal.com/article/4600 i wymagaja zainstalowanego pakietu
   gpm-devel.

   Teraz po kolei przedstawie i omowie czynnosci, jakie nalezy wykonac.
   Rzecz jasna, serwer myszy musi byc uruchomiony.
    1. Otwarcie polaczenia.
       Aby otworzyc polaczenie z serwerem, nalezy najpierw odpowiednio
       wypelnic strukture Gpm_Connect postaci:
       (przeskocz strukture Gpm_Connect)
        struc Gpm_Connect
                .eventMask      resw    1
                .defaultMask    resw    1
                .minMod         resw    1
                .maxMod         resw    1
                .pid            resd    1
                .vc             resd    1
        endstruc
       Nas interesuja tylko 4 pierwsze pola.
       Do "eventMask" wstawimy NOT 0 (czyli -1), a do "defaultMask" zero,
       co oznacza, ze interesuja nas wszystkie typy zdarzen.
       Do "minMod" wstawimy 0, a do "maxMod" - NOT 0, co oznacza, ze
       interesuja nas wszystkie klawisze modyfikujace (Ctrl, Alt, ...).
       Po wypelnieniu struktury uruchamiamy funkcje Gpm_Open z dwoma
       parametrami: adres naszej struktury oraz wartosc 0. Jesli wystapi
       blad, Gpm_Open zwroci wartosc -1. Typowa przyczyna jest brak
       uprawnien do pliku gniazda serwera. Jako root nalezy wtedy wpisac
       "chmod o+rwx /dev/gpmctl".
       Jesli chcemy, aby kursor myszy byl widoczny, nalezy do zmiennej
       globalnej serwera gpm_visiblepointer wstawic wartosc 1.
    2. Ustalenie wlasnej funkcji obslugi zdarzen (komunikatow odbieranych
       przez serwer od myszy).
       Nic prostszego. Do zmiennej globalnej gpm_handler wpisujemy adres
       naszej procedury. Dzieki temu serwer bedzie wiedzial, gdzie jest
       funkcja, ktora nalezy uruchomic, gdy nastapi zdarzenie.
    3. Oczekiwanie na zdarzenia.
       Aby odbierac zdarzenia, nalezy skorzystac z funkcji Gpm_Getc.
       Przyjmuje ona jeden argument: adres struktury FILE opisujacej
       plik, z ktorego odbierane maja byc zdarzenia. Wpisujemy tam
       standardowe wejscie (jako zmienna z biblioteki jezyka C). Jesli
       nastapi jakies zdarzenie, nasza procedura obslugi zdarzen zostanie
       automatycznie uruchomiona z wlasciwymi parametrami.
    4. Zamykanie polaczenia.
       Robimy to zwykle wtedy, gdy Gpm_Getc otrzymala znak konca pliku
       (DWORD -1). Samo zamkniecie polega na uruchomieniu funkcji
       Gpm_Close (bez argumentow).
     _________________________________________________________________

Procedura obslugi zdarzen

   Jest to kluczowa funkcja obslugi myszy. Dostaje ona na stosie dwa
   argumenty:
    1. pierwszy od lewej (ostatni wkladany na stos) - wskaznik do
       struktury Gpm_Event, opisujacej dane zdarzenie.
    2. drugi od lewej - wskaznik do dodatkowych danych

   Jesli w swojej procedurze chcecie tylko cos wyswietlic, to te
   argumenty nie sa wam potrzebne. Ale struktura Gpm_Event niesie ze soba
   wiele przydatnych informacji, ktore teraz objasnie.

   Sama struktura wyglada tak:
   (przeskocz strukture Gpm_Event)
        struc Gpm_Event
                .buttons        resb    1
                .modifiers      resb    1
                .vc             resw    1
                .dx             resw    1
                .dy             resw    1
                .x              resw    1
                .y              resw    1
                .type           resd    1
                .clicks         resd    1
                .margin         resd    1
                .wdx            resw    1
                .wdy            resw    1
        endstruc

   Pole "buttons" mowi o tym, ktore przyciski zostaly nacisniete.
   Wystarczy uzyc instrukcji TEST z jedna z wartosci GPM_B_* (podanych w
   programie).
   Pole "modifiers" mowi o tym, ktore z klawiszy modyfikujacych byly
   aktywne w chwili zdarzenia. Mozna uzyc instrukcji TEST z jedna z
   wartosci (1 << KG_*).
   Pola X oraz Y to oczywiscie wspolrzedne zdarzenia (w czasie ruchu
   zmieniaja sie).
   I najwazniejsze chyba pole: "type", opisujace rodzaj zdarzenia.
   Uzyjcie instrukcji TEST z jedna z wartosci GPM_*, aby okreslic typ
   zdarzenia. Dla klikniecia najczesciej bedzie to wartosc 20 (czyli
   16+4) - klikniecie jednym przyciskiem.
     _________________________________________________________________

Przykladowy program

   Aby poskladac cala te wiedze i pozwolic Wam uchronic sie od czesci
   bledow, przedstawiam ponizej przykladowy program. Jego zadaniem jest
   wypisanie stosownego napisu w punkcie klikniecia (najlepiej testowac
   na konsoli tekstowej). Wykorzystane w programie sekwencje kontrolne
   terminala sa omowione w artykule Bezposredni dostep do ekranu.

   Program zakonczy dzialanie po odebraniu znaku konca pliku (na konsoli
   nalezy nacisnac Ctrl+D).
   (przeskocz program reagujacy na mysz)
; Programowanie myszy w asemblerze z wykorzystaniem GPM
;
; autor: Bogdan D., bogdandr (at) op.pl
;
; kompilacja:
; nasm -O999 -f elf -o mysz.o mysz.asm
; gcc -o mysz mysz.o -lgpm

section .text
global  main

; struktura sluzaca polaczeniu sie z serwerem (/usr/linux/gpm.h)
struc Gpm_Connect
        .eventMask      resw    1
        .defaultMask    resw    1
        .minMod         resw    1
        .maxMod         resw    1
        .pid            resd    1
        .vc             resd    1
endstruc

; struktura opisujaca zdarzenie myszy: ruch, klikniecie itp.
struc Gpm_Event
        .buttons        resb    1
        .modifiers      resb    1
        .vc             resw    1
        .dx             resw    1
        .dy             resw    1
        .x              resw    1
        .y              resw    1
        .type           resd    1
        .clicks         resd    1
        .margin         resd    1
        .wdx            resw    1
        .wdy            resw    1
endstruc

; przyciski (pole "buttons" w Gpm_Event)

; FASM: GPM_B_* = x
%define GPM_B_DOWN      32              ; nacisniecie przycisku
%define GPM_B_UP        16              ; zwolnienie przycisku
%define GPM_B_FOURTH    8
%define GPM_B_LEFT      4
%define GPM_B_MIDDLE    2
%define GPM_B_RIGHT     1
%define GPM_B_NONE      0

; typy zdarzen (pole "type" w Gpm_Event)

; FASM: GPM_* = x
%define   GPM_MOVE      1
%define   GPM_DRAG      2
%define   GPM_DOWN      4
%define   GPM_UP        8
%define   GPM_SINGLE    16
%define   GPM_DOUBLE    32
%define   GPM_TRIPLE    64
%define   GPM_MFLAG     128
%define   GPM_HARD      256
%define   GPM_ENTER     512
%define   GPM_LEAVE     1024

; numery bitow klawiszy (pole "modifiers" w Gpm_Event)
; z /usr/include/linux/keyboard.h

; FASM: KG_* = x
%define KG_SHIFT        0
%define KG_CTRL         2
%define KG_ALT          3
%define KG_ALTGR        1
%define KG_SHIFTL       4
%define KG_SHIFTR       5
%define KG_CTRLL        6
%define KG_CTRLR        7
%define KG_CAPSSHIFT    8


; FASM: extrn zamiast extern
extern  Gpm_Open        ; funkcja otwierajaca polaczenie z serwerem
extern  Gpm_Close       ; funkcja zamykajaca polaczenie z serwerem
extern  Gpm_Getc        ; funkcja pobierajaca znak i uruchamiajaca
                        ; obsluge zdarzen
extern  gpm_handler     ; tu wpiszemy adres naszej funkcji
                        ; obslugi zdarzen myszy
extern  stdin           ; standardowe wejscie, skad bedziemy czytac znaki
extern  gpm_visiblepointer      ; zmienna mowiaca o tym,
                                ; czy kursor jest widoczny

main:
        mov     eax, 4
        mov     ebx, 1
        mov     ecx, czysc
        mov     edx, czysc_dl
        int     80h                     ; czyscimy ekran

        mov     dword [gpm_visiblepointer], 1   ; kursor ma byc widoczny

                        ; akceptujemy wszystkie zdarzenia myszy
        mov     word [conn + Gpm_Connect.eventMask], ~ 0
                        ; FASM: not 0
        mov     word [conn + Gpm_Connect.defaultMask], 0

                        ; akceptujemy wszystkie klawisze modyfikujace
        mov     word [conn + Gpm_Connect.minMod], 0
        mov     word [conn + Gpm_Connect.maxMod], ~ 0
                        ; FASM: not 0

        push    dword 0
        push    dword conn
        call    Gpm_Open                ; otwieramy polaczenie z serwerem
        add     esp, 8                  ; usuwamy argumenty ze stosu

        cmp     eax, -1                 ; EAX = -1 oznacza blad
        jne     .jest_ok

        mov     ebx, eax
        mov     eax, 1
        int     80h                     ; zwracamy kod -1

.jest_ok:
                        ; wpisujemy adres naszej funkcji:
        mov     dword [gpm_handler], obsluga

.czytaj:
                        ; wczytujemy znak z klawiatury lub zdarzenie myszy
        push    dword [stdin]
        call    Gpm_Getc                ; pobieramy znak/zdarzenie.
                ; Funkcja obslugi zostanie uruchomiona automatycznie.
        add     esp, 4

        cmp     eax, -1                 ; -1 oznacza EOF, koniec pliku
        jne     .czytaj

        call    Gpm_Close               ; zamykamy polaczenie z serwerem

        mov     eax, 4
        mov     ebx, 1
        mov     ecx, czysc
        mov     edx, czysc_dl
        int     80h                     ; czyscimy ekran

        mov     bx, 1
        mov     cx, 1
        call    zmiana_poz              ; zmiana pozycji kursora tekstowego

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




; procedura obslugi zdarzenia myszy. otrzymuje 2 argumenty:
; wskaznik na zdarzenie i wskaznik na dane
; prototyp w C wyglada tak:
; int obsluga( Gpm_Event *ev, void *dane );

obsluga:
        push    ebp
        mov     ebp, esp
                        ; [ebp] = stary EBP
                        ; [ebp+4] = adres powrotny
                        ; [ebp+8] = pierwszy parametr (wsk. na zdarzenie)
                        ; [ebp+12] = drugi parametr (wsk. na dane)

%define         ev      ebp+8
%define         dane    ebp+12

                                ; interesuja nas tylko klikniecia:
        mov     eax, [ev]       ; [ev], a nie ev, jest wskaznikiem
                                ; na strukture
        test    dword [eax + Gpm_Event.type], GPM_DOWN
        jz      .koniec

        push    ebx
        push    ecx
        push    edx

                                        ; wyswietlimy typ zdarzenia
        mov     ecx, [ev]
        mov     eax, [ecx + Gpm_Event.type]
                                ; cala zawartosc i tak miesci sie w AX
        xor     edx, edx
        mov     bx, 1000
        div     bx              ; maksymalny typ to 1024, wiec zaczynamy
                                ;  dzielenie od 1000
        add     "0"             ; zmiana wyniku na ASCII
        mov     [numer], al

        mov     eax, edx        ; EAX = reszta z poprzedniego dzielenia
        mov     bl, 100
        div     bl
        add     al, "0"
        mov     [numer+1], al

        shr     ax, 8           ; AX = AH = reszta z poprzedniego dzielenia
        mov     bl, 10
        div     bl
        add     ax, "00"
        mov     [numer+2], ax   ; na miejsce trzecie wstawiamy iloraz,
                                ; a na czwarte - od razu reszte


        mov     dh, 10
        mov     ax, [ecx + Gpm_Event.x] ; x = kolumna
        div     dh
        add     ax, "00"                ; zamiana ilorazu i reszty na ASCII
        mov     [miejsce+4], ax         ; od razu wstawiamy iloraz i reszte

        mov     ax, [ecx + Gpm_Event.y] ; y = wiersz
        div     dh
        add     ax, "00"
        mov     [miejsce+1], ax

        mov     bx, [ecx + Gpm_Event.x]
        mov     cx, [ecx + Gpm_Event.y]
        call    zmiana_poz      ; zmiana pozycji kursora tekstowego

        mov     eax, 4
        mov     ebx, 1
        mov     ecx, zdarz
        mov     edx, zdarz_dl
        int     80h             ; na ustalonej przed chwila pozycji
                                ; piszemy info o zdarzeniu

        pop     edx
        pop     ecx
        pop     ebx

.koniec:
        xor     eax, eax        ;zerowa wartosc zwracana oznacza brak bledu
        leave                   ; przywracamy ESP i EBP
        ret



; procedura zmiany pozycji kursora tekstowego (nie myszy)
; wykorzystuje sekwencje kontrolne terminala
zmiana_poz:

;  BX = nowa kolumna
;  CX = nowy wiersz

        push    eax
        push    ebx
        push    ecx
        push    edx

        mov     dh, 10
        mov     ax, bx                  ; AX = kolumna znaku
        and     ax, 0FFh
        div     dh                      ; dzielimy przez 10
        add     ax, "00"                ; zmiana ilorazu i reszty na ASCII
        mov     [kolumna], ax           ; zapisanie ilorazu i reszty

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

        mov     eax, 4
        mov     ebx, 1
        mov     ecx, pozycja
        mov     edx, pozycja_dl
        int     80h             ; wyswietlenie sekwencji kontrolnej

        pop     edx
        pop     ecx
        pop     ebx
        pop     eax

        ret


section .data

zdarz           db      "Zdarzenie: "
numer           db      "xxxx w "       ; typ zdarzenia
miejsce         db      "(ww,kk)"       ; pozycja na ekranie
zdarz_dl        equ     $ - zdarz


ESC             equ     1Bh             ; kod ASCII klawisza Escape

                                        ; sekwencja zmiany pozycji kursora
pozycja         db      ESC, "["
wiersz          db      "00;"
kolumna         db      "00H"
pozycja_dl      equ     $ - pozycja

czysc           db      ESC, "[2J"      ; sekwencja czyszczenia ekranu
czysc_dl        equ     $ - czysc

section .bss

        ; conn jest struktura typu Gpm_Connect:
conn            istruc  Gpm_Connect

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