C dla każdego (cz. 26.)
Ulepszanie systemu - COMMODITY (cz. 1)
W kolejnych odcinkach zajmiemy się zagadnieniami
"ulepszania" systemu. Zaczynamy od biblioteki
"commodities.library".
W amigowcach jakoś tak "od zawsze" tkwiła chęć
poprawiania systemu. Może dlatego, że to komputer
używany głównie przez entuzjastów, a może po
prostu dlatego, że projektanci systemu dali nam
taką możliwość. Jakiekolwiek by nie były
przyczyny, tego typu programów jest mnóstwo i
ciągle powstają nowe, coraz bardziej rozbudowane.
Ważnym etapem na drodze ich rozwoju było
pojawienie się w wersji 2.0 systemu operacyjnego
nowej biblioteki, "commodities.library". Oferuje
ona proste mechanizmy interakcji ze strumieniem
wejściowym oraz zunifikowany interfejs sterujący
tego typu programami, w postaci programu
"Exchange" z katalogu "Tools/ Commodities".
"Exchange" stanowi zresztą namiastkę znanego z
innych systemów operacyjnych "Task Managera",
umożliwiając np. ikonifikację lub zakończenie
aplikacji. Dlatego też coraz popularniejsze staje
się wbudowywanie w co poważniejsze programy
obsługi "commodities" tylko po to, aby można było
programem sterować z poziomu "Exchange" - tak robi
to np. biblioteka MUI.
Wróćmy jednak do tej "interakcji ze strumieniem
wejściowym". Ważną rolę w AmigaOS odgrywa strumień
wejściowy (ang. input stream). Płyną nim
informacje o zdarzeniach wejściowych, np.
naciśnięciu klawisza na klawiaturze, ruszeniu
myszą, włożeniu dyskietki itp. Na drodze tego
strumienia znajdują się sterowniki wejściowe (ang.
input handlera). Wyłapują one płynące informacje,
analizują je i modyfikują. Wśród tych sterowników
jest również sterownik biblioteki Intuition: na
podstawie napływających informacji uaktywnia on
okna, przełącza ekrany itd. istnieje możliwość
podłączenia własnych sterowników przed ten z
biblioteki Intuition: tak właśnie postępuje
biblioteka "commodities.library". Do używających
jej programów mogą więc np. docierać informacje o
wciśniętych klawiszach bez względu na to, które
okno jest aktywne:umożliwia to tworzenie tzw.
hotkeys. Programy mogą również modyfikować
strumień zdarzeń wejściowych, np. zmieniając
znaczenie sekwencji klawiszy na klawiaturze, co
zaprezentujemy w naszym przykładzie.
Po otwarciu biblioteki "commodities. library",
najważniejszą rzeczą jest utworzenie tzw. brokera:
Cx0bj *CxBroker ( struct NewBroker *nb, LONG
*error );
Pierwszym parametrem tej funkcji jest wskaźnik na
zainicjowaną strukturę "NewBroker", zdefiniowaną w
pliku "libraries/commodities.h":
struct NewBroker
{
BYTE nb_Version;
STRPTR nb_Name;
STRPTR nb_Title;
STRPTR nb_Descr;
WORD nb_Unique;
WORDnb_Flags;
BYTE nb_Pri;
struct MsgPort*
nb_Port;
WORD nb_ReservedChannel;
};
Omówmy po kolei jej pola:
nb_Version: podajemy NB_VERSION: informuje to
bibliotekę, z którą wersją struktury ma do
czynienia.
nb_Name, nb_Title, nb_Descr: napisy identyfikujące
aplikację. Są one umieszczane w programie
"Exchange" odpowiednio: w liście, w pierwszej i w
drugiej linii opisu.
nb_Unique: ustawienie flagi NBU_UNIQUE
uniemożliwia utworzenie więcej niż jednego brokera
o tej samej nazwie, a więc zwykle działania więcej
nit jednej kopii programu naraz. Dodatkowo
ustawienie flagi NBU_NOTIFY powoduje wysłanie do
już istniejącego brokera informacji o próbie
utworzenia nowego. Jest to zwykle używane do
zakończenia programu albo otwarcia okna, o ile
program takowe posiada (nasz przykład tak).
nb_Flags: ustawienie flagi COF_SHOW_HIDE oznacza,
że program posiada okno, co odblokowuje przyciski
pokazywania/chowania interfejsu w oknie programu
"Exchange".
nb_Pri: priorytet brokera. Zwykle 0, ale w naszym
przykładzie 127 (najwyższy możliwy), gdyż zależy
nam, aby nasz broker wykonywał się przed innymi.
Standardem jest, że priorytet może być zmieniany
przez użytkownika z użyciem opcji o nazwie
"CX_PRIORITY".
nb_Port: port, do którego mają być wysyłane
informacje.
nb_ReservedChannel: nie używane.
Drugim parametrem funkcji CxBroker() jest wskaźnik
na zmienną, w której zostanie odnotowany kod ew.
błędu, lub NULL, gdy nie jesteśmy nim
zainteresowani. Funkcja zwraca wskaźnik na
utworzony obiekt lub NULL przy niepowodzeniu. W
tym drugim przypadku sprawdzamy, czy było ono
spowodowane przez już istniejący broker (błąd
CBERR_DUP).
Każdy Cx0bj posiada listę, do której można
dołączyć Inne obiekty, Gdy przybywa zdarzenie
wejściowe, biblioteka "commodities" przekazuje je
wszystkim brokerom w kolejności priorytetów. Każdy
broker przekazuje je z kolei dołączonym do siebie
obiektom, w takiej kolejności, w jakiej znajdują
się one na liście. W naszym programie dołączamy do
brokera dwa obiekty: własny i filtrujący.
Obiekt własny (ang. custom) tworzymy przez
wywołanie CxCustom(). CxCustom() nie jest funkcją,
tylko makrem preprocesora zdefiniowanym w pliku
"librarles/commodities.h". Akceptuje ono dwa
parametry:
wskaźnik na funkcję oraz parametr typu "LONG",
który możemy wykorzystać do przekazania prywatnych
informacji (patrz niżej).
Funkcja, której adres przekazujemy, będzie
wywoływana dla każdego zdarzenia wejściowego. Jest
ona wywoływana w kontekście innego zadania -
"Input.device". W takim wypadku należy zachować
szczególną ostrożność: funkcja nie może sprawdzać
przepełnienia stosu oraz, o ile używamy bliskiego
modelu danych, powinna na starcie ustawić rejestr
"a4". W kompilatorach GCC i SAS/C zapewniają to
odpowiednio kwalifikatory: "_interrupt" i
"_saveds". Funkcja otrzymuje dwa parametry -
wskaźniki na "CxMsg" oraz "Cx0bj", będący naszym
własnym obiektem. Oba parametry przekazywane są na
stosie: jeżeli kompilator skonfigurowano, aby
przekazywał parametry przez rejestry, należy
wymusić kwalifikatorem "_stdargs" oczekiwanie ich
dla tej funkcji na stosie. Tu mała uwaga:
przynajmniej w wersji 6.57 kompilatora SAS/C, gdy
użyje się "_stdargs", ignorowany jest
"_interrupt", co W naszym przypadku ma fatalne
konsekwencje. Należy więc globalnie wyłączyć
sprawdzanie przepełnienia stosu, używając opcji
"NOSTKCHK". A tak w ogóle, to "input. device" jest
zadaniem strategicznym i "obce" funkcje powinny
się w nim wykonywać tak szybko, jak to tylko
możliwe, a i nie wszystkie chwyty są dozwolone.
Nawet nie próbujcie robić jakichś "kontrolnych
printf()ów": zwis murowany.
Wracając do naszej funkcji i jej parametrów: z
"CxMsg" przy użyciu funkcji CxMsgData() możemy
wyłuskać wskaźnik na strukturę "InputEvent"
opisującą zdarzenie wejściowe, zdefiniowaną w
"devices/inputevent.h". Zadaniem naszego programu
jest emulacja pewnego zachowania klawisza "Caps
Lock" klawiatury PCtowej: mianowicie, gdy włączony
jest "Caps Lock" i wciśnie się "Shift", wypisywane
są małe litery. Ludzie różne dziwne rzeczy lubią,
więc pewnie są i wielbiciele tego "patentu". Pole
"ie_Class" struktury "lnputEvent" zawiera klasę
zdarzenia: nas interesuje IECLASS_RAWKEY. Pole
"ie_Qualifier" opisuje okoliczności zdarzenia: gdy
zaszło ono przy włączonym "CapsLock" i wciśniętym
którymś "Shift", po prostu czyścimy zarówno
"CapsLock", jak i "Shift". l na koniec; z "CxMsg"
możemy przy użyciu funkcji CxMsgID() wyłuskać
wartość drugiego z parametrów przekazanych do
CxCustom() podczas tworzenia obiektu.
Ponieważ nie chcieliśmy dzielić dość sporego
listingu na dwie części, na tym musimy już
niestety na dziś zakończyć. Za miesiąc dokończymy
opis.
Listing nr S22