Knihovna Xlib
Knihovna Xlib zajišťuje elementární funkce X na straně klienta. Aplikace se typicky nepíší pouze s použitím Xlib, protože takové programování je značně nepohodlné, jak uvidíme dále. Na rozdíl od toolkitů Xlib neposkytuje widgety, tj. hotové prvky uživatelského rozhraní, které mají vlastní logiku, umí se nakreslit a umí samy o sobě komplexně reagovat na události. Např. editační řádek v GTK+ nebo v Qt po stisku klávesy přidá znak do interní reprezentace zobrazeného textu, nakreslí ho na obrazovku a pomocí signálu dá vědět zbytku aplikace o změně svého stavu. V Xlib máme k dispozici pouze funkce pro vytvoření oken a manipulaci s nimi, pro kreslení a pro příjem událostí. Implementace editačního řádku bude vypadat tak, že si aplikace vytvoří okno, nahraje font a bude přijímat události. V reakci naExpose překreslí okno a KeyPress způsobí přidání znaku a překreslení. Kdybychom chtěli dosáhnout stejné funkčnosti, jakou poskytují toolkity, museli bychom se postarat ještě o další věci, jako např. cut&paste.
Xlib se příliš nehodí pro aplikační programování. Při psaní aplikace je lepší použít vhodný toolkit. Občas však může být nutné volat funkce Xlib pro operace, které nemají podporu v toolkitu. Např. historicky první X Toolkit neměl vlastní kreslicí operace a pro veškeré kreslení se používaly funkce z Xlib. Bez použití knihovny Xlib se samozřejmě neobejdou programátoři toolkitů. Ale i pro toho, kdo nikdy funkce Xlib přímo nepoužívá, je vhodné znát principy fungování této knihovny, aby pro něj toolkit nebyl jen černá skříňka.
Základní náplní práce Xlib je překlad mezi C-čkovým API a zprávami X protokolu. X protokol používá streamový soket. Lokálně se komunikuje přes unixový soket /tmp/.X11-unix/Xd, kde d je číslo displeje. Pro síťový přístup k X serveru se používá TCP soket na portu 6000+d. Spojení vždy navazuje klient směrem k serveru. Pokud server podporuje Shared Memory Extension a klient i server jsou na stejném počítači, je možné komunikaci urychlit tím, že se některá data (obrázky) přenášejí přes sdílenou paměť.
V X protokolu existují čtyři typy paketů. Požadavky (requests) posílá klient serveru. Generuje je většina volání Xlib, např. funkce pro kreslení čáry, vytvoření okna nebo dotaz na velikost okna. Pokud cílem požadavku není získat nějakou hodnotu od X serveru, server na požadavek neodpovídá. Požadavky se neposílají jednotlivě, ale ukládají se do bufferu na straně klienta. Celý buffer se pošle na server najednou, když je plný. Obsah bufferu se také posílá, jestliže program nemá ve frontě žádnou čekající událost a zavolá funkci Xlib pro čekání na událost (XNextEvent) nebo jestliže se volá funkce, která vyžaduje odpověď od serveru. Tím je zajištěno, že než klient začne čekat, server např. vytvoří a nakreslí okno, v němž chce aplikace přijímat události. Poslední možností je explicitně vynutit okamžité odeslání bufferu požadavků funkcemi XFlush nebo XSync. To se hodí např. při vykreslování animací, kdy je nutné obnovit obsah okna v určitý čas nezávisle na příchozích událostech. Dávková komunikace se serverem se dá vypnout funkcí XSynchronize. Xlib pak posílá každý požadavek ihned a čeká, zda server nevrátí chybu. Synchronní režim není vhodné běžně používat, protože je několikanásobně pomalejší. Hodí se však pro ladění, aby klient nedostával chyby asynchronně a se zpožděním.
Druhý typ paketů jsou odpovědi serveru (replies) na některé požadavky klienta, např. na dotaz o vlastnostech okna. Server klientovi dále posílá události (events) informující o uživatelském vstupu, změně rozložení oken, nebo obsahující zprávy od jiného klienta. Poslední typ paketů jsou chyby (errors), které informují o tom, že se nepovedl některý z předchozích požadavků. Díky dávkovému zpracování požadavků to nemusí být ten poslední. Chybu zachytí Xlib a zavolá na ni chybový handler, jenž standardně vypíše chybové hlášení a ukončí program.
X server spravuje systémové zdroje poskytované klientům pro použití v jejich GUI. Typy zdrojů X serveru jsou: okno (window), pixmapa (pixmap), paleta (colormap), kurzor (cursor), font a grafický kontext (graphics context). Klienti se na ně odkazují pomocí číselného identifikátoru (ID). Tím, že jsou data jako parametry oken, obrázky, znaky z fontu, apod. uložena v paměti serveru, redukuje se objem dat přenášených mezi klientem a serverem. S libovolným zdrojem X serveru může manipulovat každý klient, který zná jeho ID. Program tak může kreslit nebo číst události v cizích oknech. Při odpojení klienta od serveru zruší X server všechny zdroje vytvořené odpojeným klientem.
Hello Xlib!
Pro úvodní seznámení s funkcemi Xlib si jako obvykle předvedeme program ve stylu „Hello World“. Program vytvoří jediné okno a v něm nakreslí obdélník a vypíše několik řetězců, viz obr. 1. Po stisku klávesy nebo tlačítka myši program skončí. Celý zdrojový text je v souboru hello.c. Soubor hello_icon.h obsahuje definici ikony. Program přeložíme a slinkujeme příkazem
gcc -o hello hello.c -I/usr/X11R6/include -L/usr/X11R6/lib -lX11
Tato varianta programu „Hello World“ pochází z knihy Adrian Nye: Xlib Programming Manual (O'Reilly and Associates, Inc., 1992). Poněkud překvapující je délka programu 353 řádek. To je dost značný rozdíl oproti programu pro GTK+ z 2. dílu (91 řádek) a Qt ze 7. dílu (14 řádek). Již toto srovnání cosi napovídá o (ne)vhodnosti Xlib pro běžné aplikační programování. Z důvodu velikosti programu ho zde nebudeme podrobně rozebírat řádek po řádku.
Obr. 1: Hello World v Xlib
Ve funkci main se klient nejprve připojí k X serveru voláním XOpenDisplay. Po úspěšném navázání spojení vytvoří okno pomocí XCreateSimpleWindow. K oknu přísluší ikona vytvořená funkcí XCreateBitmapFromData. Dále je třeba sdělit window manageru (pomocí XSetWMProperties) některé informace o oknu, např. titulek okna a ikony, odkaz na pixmapu ikony a minimální velikost okna. Před zahájením zpracování událostí je nutné říct X serveru, které události má klientovi posílat – XSelectInput. Před zahájením kreslení je třeba načíst font funkcí XLoadQueryFont, vytvořit grafický kontext funkcíXCreateGC a nastavit jeho parametry: XSetFont, XSetForeground, XSetLineAttributes a XSetDashes. Následuje namapování (zobrazení) okna pomocí XMapWindow. Tím je dokončena inicializace programu a může být zahájen cyklus zpracování událostí.
Program vždy počká na událost a přečte ji voláním XNextEvent. Pak se rozhodne podle typu události (event.type). Událost Exposeje obsloužena nakreslením obsahu okna s využitím funkcí XDrawString a XDrawRectangle. Událost ConfigureNotify informuje o změně velikosti okna a program na ni reaguje přepočítáním umístění grafiky v okně a překreslením. Stisk klávesy (KeyPress) nebo tlačítka myši (ButtonPress) způsobí ukončení programu. Před ukončením se uvolní alokované zdroje X serveru (XUnloadFont, XFreeGC) a klient zavře spojení se serverem (XCloseDisplay). Tyto úklidové akce nejsou nezbytné, protože ukončení procesu způsobí zavření soketu, které server detekuje a uvolní veškeré zdroje alokované klientem. Nicméně je vhodné volat alespoň XCloseDisplay, aby klient dostal případné chyby, které mu server zatím neposlal.
Okna
Okno je obdélníková oblast na obrazovce, ve které lze kreslit a přijímat události. Pokud server a Xlib podporují rozšíření Shape, je možné vytvářet okna libovolných tvarů. Na každé obrazovce tvoří okna stromovou strukturu. Kořen stromu je kořenové (root) okno existující po celou dobu běhu X serveru. Do root okna lze kreslit a lze v něm přijímat události, ale není možné změnit jeho velikost nebo pozici. Kromě vztahů předků a potomků (ancestors, descendants) je ve stromě oken mezi sourozenci (synovskými okny stejného rodiče) definován tzv. stacking order určující, jak se tito sourozenci navzájem zakrývají. Vztahy ve stromě oken jsou znázorněny na obr. 2.
Obr. 2: Strom oken a stacking order
Souřadnice uvnitř okna se počítají od levého horního rohu doprava a dolů. Pozice okna se zadává relativně vůči rodiči. Okno může mít navíc okraj (rámeček). Počátek souřadnic okna je v takovém případě uvnitř rámečku, avšak pozice se vztahuje k bodu vně rámečku. Okraje oken se ale moc nepoužívají. Na obr. 3 jsou zobrazeny jednotlivé rozměry tvořící geometrii oken.
Obr. 3: Geometrie oken
Po vytvoření existuje okno pouze jako datová struktura v paměti X serveru. Aby bylo vidět na obrazovce, je nutné ho namapovat funkcí XMapWindow a musí být namapováni také všichni jeho předci. Ani namapované okno nemusí být vidět, pokud leží mimo plochu svého rodiče nebo je zakryto svými sourozenci nebo sourozenci předků. Okno se typicky neobjeví hned po zavolání XMapWindow, protože kvůli dávkové komunikaci se serverem může požadavek nějakou dobu čekat v bufferu klienta. U top-level oken navíc do mapování vstupuje window manager.
Každé okno má sadu charakteristik zadávaných při vytvoření okna voláním XCreateWindow nebo XCreateSimpleWindow. Později je lze zjišťovat a některé i měnit pomocí funkcí Xlib (XReparentWindow, XGetGeometry, XGetWindowAttributes, XChangeWindowAttributes atd.). Umístění ve stromě oken a na obrazovce určují rodičovské okno a konfigurace okna (pozice, šířka, výška, šířka okraje a pozice v rámci stacking order). Visual
definuje způsob přepočtu hodnot jednotlivých pixelů na barvy podle barevné palety. Počet bitů použitých na každý pixel udává bitová hloubka. Okno má třídu, která může být InputOutput (do okna lze kreslit a lze v něm přijímat události), nebo InputOnly (v okně lze přijímat události, ale nelze do něj kreslit a okno není vidět). InputOnly se hodí, pokud potřebujeme speciální obsluhu událostí nebo jiný tvar kurzoru v určité oblasti okna. Zde je třeba zmínit, že neexistují žádná OutputOnly okna, protože okno s libovolným grafickým obsahem musí reagovat na požadavky serveru o překreslení, zasílané klientovi ve formě událostí Expose. Poslední z charakteristik okna jsou
atributy.Atributů okna je celá řada. Dá se nastavit barva pozadí okna, nebo lze alternativně na pozadí umístit pixmapu. Pokud má okno okraj, dá se nastavovat i jeho barva či pixmapa. Hodnota bit gravity určuje, co se stane s obsahem okna při změně velikosti. Standardní hodnota je ForgetGravity, tj. okno se smaže a musí se překreslit. Další možnosti jsou znázorněny na obr. 4. Původní obsah se zachová a přesune se relativně vůči počátku okna. Pro nepokryté oblasti X server vygeneruje události Expose, a tím zajistí jejich vykreslení. Server nemusí bit gravity implementovat a může vždy obsah okna celý smazat a nechat překreslit. Window gravity říká, co se má při změně velikosti stát se synovskými okny. Standardní je NorthWestGravity, tj. relativní poloha vůči počátku okna se nemění. Další možnosti jsou UnmapGravity (synovská okna se odmapují), StaticGravity (nemění se poloha vzhledem ke kořenovému oknu) a hodnoty z obr. 4. Hodnota backing store říká, zda si má X server pamatovat obsah okna, pokud je překryto jiným oknem. Po odkrytí pak může okno obnovit, aniž by posílal událost Expose
klientovi. Duální funkci má atribut save under, který určuje, zda si má X server pamatovat obsah okna překrytého oknem s nastaveným save under. To se hodí pro okna, která jsou na obrazovce zobrazena jen krátce, jako např. popup menu. Server nemusí backing store ani save under podporovat. V každém okně si může klient nastavit masku událostí, které chce v tomto okně přijímat. Maska je samostatná pro každé okno a klienta, tudíž různí klienti mohou ve stejném okně přijímat různé množiny událostí. Jestliže má jednu událost vybranou více klientů, každý z nich dostane její kopii. Události z myši a klávesnice, jež nejsou obslouženy v okně, ve kterém vznikly, se propagují směrem ke kořeni. Propagace se zastaví, pokud některé okno na cestě událost přijme, nebo událost dorazí do kořenového okna či do okna, ve kterém je její propagace zakázána atributem do_not_propagate_mask. Zobrazování a manipulace s top-level okny je obvykle řízena window managerem. Některá okna – popup menu, tooltipy, apod. – se potřebují vyhnout interakci s window managerem, což zařídí atribut override_redirect. Zbývající atributy okna jsou
barevná paleta a tvar kurzoru.
Obr. 4: Bit a window ravity