Wbudowanie obsługi myszy do swoich programów wcale nie musi być trudne. Nie musimy bawić się
w pisanie sterowników do każdej możliwej myszki ani bezpośrednio rozmawiać
ze sprzętem.
Cała ta robota została już za nas zrobiona. Komunikacją z myszą zajmie się serwer myszy GPM,
a my zajmiemy się tylko uruchamianiem odpowiednich funkcji, jakie ten serwer oferuje.
Podane tu przeze mnie informacje pochodzą z moich własnych interpretacji pliku /usr/include/gpm.h oraz ze świetnego artykułu linuxjournal.com/article/4600 i wymagają zainstalowanego pakietu gpm-devel.
Teraz po kolei przedstawię i omówię czynności, jakie należy wykonać. Rzecz jasna, serwer myszy musi być uruchomiony.
Aby otworzyć połączenie z serwerem, należy najpierw odpowiednio wypełnić strukturę Gpm_Connect postaci:
(przeskocz strukturę 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 interesują tylko 4 pierwsze pola.
Do eventMask
wstawimy NOT 0
(czyli -1), a do
defaultMask
zero, co oznacza,
że interesują nas wszystkie typy zdarzeń.
Do minMod
wstawimy 0, a do maxMod
- NOT 0
, co oznacza, że interesują
nas wszystkie klawisze modyfikujące (Ctrl, Alt, ...).
Po wypełnieniu struktury uruchamiamy funkcję Gpm_Open
z dwoma parametrami: adres naszej struktury oraz wartość 0. Jeśli
wystąpi błąd, Gpm_Open zwróci wartość -1.
Typową przyczyną jest brak uprawnień do pliku gniazda serwera. Jako
root należy wtedy wpisać chmod o+rwx /dev/gpmctl
.
Jeśli chcemy, aby kursor myszy był widoczny, należy do zmiennej
globalnej serwera gpm_visiblepointer wstawić wartość 1.
Nic prostszego. Do zmiennej globalnej gpm_handler wpisujemy adres naszej procedury. Dzięki temu serwer będzie wiedział, gdzie jest funkcja, którą należy uruchomić, gdy nastąpi zdarzenie.
Aby odbierać zdarzenia, należy skorzystać z funkcji Gpm_Getc. Przyjmuje ona jeden argument: adres struktury FILE opisującej plik, z którego odbierane mają być zdarzenia. Wpisujemy tam standardowe wejście (jako zmienną z biblioteki języka C). Jeśli nastąpi jakieś zdarzenie, nasza procedura obsługi zdarzeń zostanie automatycznie uruchomiona z właściwymi parametrami.
Robimy to zwykle wtedy, gdy Gpm_Getc otrzymała znak końca pliku (DWORD -1). Samo zamknięcie polega na uruchomieniu funkcji Gpm_Close (bez argumentów).
Jest to kluczowa funkcja obsługi myszy. Dostaje ona na stosie dwa argumenty:
Jeśli w swojej procedurze chcecie tylko coś wyświetlić, to te argumenty nie są wam potrzebne. Ale struktura Gpm_Event niesie ze sobą wiele przydatnych informacji, które teraz objaśnię.
Sama struktura wygląda tak:
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
mówi o tym,
które przyciski zostały naciśnięte.
Wystarczy użyć instrukcji TEST
z jedną z wartości GPM_B_* (podanych w programie).
Pole modifiers
mówi o tym, które z klawiszy modyfikujących były
aktywne w chwili zdarzenia. Można użyć instrukcji TEST
z jedną z wartości (1 << KG_*).
Pola X oraz Y to oczywiście współrzędne zdarzenia (w czasie ruchu zmieniają się).
I najważniejsze chyba pole: type
, opisujące rodzaj zdarzenia.
Użyjcie instrukcji TEST
z jedną z wartości GPM_*, aby określić typ zdarzenia.
Dla kliknięcia najczęściej będzie to wartość 20 (czyli 16+4) - kliknięcie jednym przyciskiem.
Aby poskładać całą tę wiedzę i pozwolić Wam uchronić się od części błędów, przedstawiam poniżej przykładowy program. Jego zadaniem jest wypisanie stosownego napisu w punkcie kliknięcia (najlepiej testować na konsoli tekstowej). Wykorzystane w programie sekwencje kontrolne terminala są omówione w artykule Bezpośredni dostęp do ekranu.
Program zakończy działanie po odebraniu znaku końca pliku (na konsoli należy nacisnąć Ctrl+D).
; 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 służąca połączeniu się 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 opisująca zdarzenie myszy: ruch, kliknięcie 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 ; naciśnięcie 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 zdarzeń (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 bitów 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 otwierająca połączenie z serwerem extern Gpm_Close ; funkcja zamykająca połączenie z serwerem extern Gpm_Getc ; funkcja pobierająca znak i uruchamiająca ; obsługę zdarzeń extern gpm_handler ; tu wpiszemy adres naszej funkcji ; obsługi zdarzeń myszy extern stdin ; standardowe wejście, skąd będziemy czytać znaki extern gpm_visiblepointer ; zmienna mówiąca o tym, ; czy kursor jest widoczny main: mov eax, 4 mov ebx, 1 mov ecx, czysc mov edx, czysc_dl int 80h ; czyścimy ekran mov dword [gpm_visiblepointer], 1 ; kursor ma być 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 modyfikujące 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 połączenie z serwerem add esp, 8 ; usuwamy argumenty ze stosu cmp eax, -1 ; EAX = -1 oznacza błąd 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 obsługi zostanie uruchomiona automatycznie. add esp, 4 cmp eax, -1 ; -1 oznacza EOF, koniec pliku jne .czytaj call Gpm_Close ; zamykamy połączenie z serwerem mov eax, 4 mov ebx, 1 mov ecx, czysc mov edx, czysc_dl int 80h ; czyścimy ekran mov bx, 1 mov cx, 1 call zmiana_poz ; zmiana pozycji kursora tekstowego mov eax, 1 xor ebx, ebx int 80h ; wyjście z programu ; procedura obsługi zdarzenia myszy. otrzymuje 2 argumenty: ; wskaźnik na zdarzenie i wskaźnik na dane ; prototyp w C wygląda 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 ; interesują nas tylko kliknięcia: mov eax, [ev] ; [ev], a nie ev, jest wskaźnikiem ; na strukturę test dword [eax + Gpm_Event.type], GPM_DOWN jz .koniec push ebx push ecx push edx ; wyświetlimy typ zdarzenia mov ecx, [ev] mov eax, [ecx + Gpm_Event.type] ; cała zawartość i tak mieści się w AX xor edx, edx mov bx, 1000 div bx ; maksymalny typ to 1024, więc 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 resztę 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 resztę 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 wartość zwracana oznacza brak błędu 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 ; wyświetlenie 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 strukturą typu Gpm_Connect: conn istruc Gpm_Connect