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