Zpracování událostí
Událost v Xlib reprezentuje union XEvent. Sdružuje struktury pro jednotlivé typy událostí. Začátek struktury každé události je stejný. Obsahuje typ události, číslo požadavku X protokolu, jenž vygeneroval událost, příznak, zda událost vygeneroval X server, nebo jiný klient funkcí XSendEvent, displej a okno, kde událost nastala. Za těmito položkami následují další položky v závislosti na typu události.
X server generuje události a ukládá je do fronty. Události z fronty server rozesílá klientům. Kopii určité události pošle všem klientům, kteří mají nastavený příjem tohoto typu událostí. Každý klient si vybírá typy událostí samostatně pro každé okno funkcí XSelectInput. Nepotřebné typy událostí je možné při zpracování prostě ignorovat, ale lepší je je nevybírat, protože pak je X server vůbec neposílá a šetří se přenosová kapacita mezi serverem a klientem.
Klient po úvodní inicializaci a vytvoření oken obvykle zahájí cyklus zpracování událostí, který vypadá zhruba takto:
while(1) { XNextEvent(display, &event); switch(event.type) { case Expose: ... case ButtonPress: ... case MappingNotify: ... default: /* ignorované události */ } }
Funkce XNextEvent vrátí první událost z fronty. Pokud je fronta prázdná, vyprázdní buffer požadavků a čeká na příchod události. Xlib definuje řadu dalších funkcí pro neblokující čtení událostí nebo pro výběr událostí z fronty na základě různých kritérií jako typ události, okno nebo obecná podmínka reprezentovaná boolovskou funkcí.
Události z myši a klávesnice se doručují do okna, v němž je kurzor myši. Pokud toto okno nemá vybraný příslušný typ události, propaguje se událost směrem k rodičovským oknům. Distribuce událostí z klávesnice dále závisí na nastavení keyboard focus. Pokud má některé okno nastavený focus, dostává veškeré události z klávesnice, které vzniknou vně tohoto okna. Uvnitř okna s focusem funguje doručování událostí normálně, tj. událost dostane potomek, v němž je kurzor. Top-level oknům přiděluje focus window manager. Aplikace, jejíž top-level okno dostalo focus, jej může přidělit potomkům top-level okna pomocí XSetInputFocus. Přitom se generují události FocusOut a FocusIn. Alternativně může aplikace ponechat focus na top-level okně a uvnitř něho používat pro distribuci událostí z klávesnice nějaký interní mechanismus. Tuto metodu používá např. GTK+.
Existují situace, kdy je potřeba zajistit, že události z myši či klávesnice bude po určitou dobu dostávat pouze jediný klient bez ohledu na to, kde událost nastala. Takový klient si nastaví tzv. grab voláním XGrabPointer nebo XGrabKeyboard. Opětné uvolnění myši a klávesnice se provede funkcemi XUngrabPointer a XUngrabKeyboard. Obvykle je potřeba, aby informaci o uvolnění tlačítka myši dostalo stejné okno, ve kterém uživatel tlačítko stiskl. Proto X server automaticky nastaví po stisku tlačítka grab myši, pokud má klient vybrané události ButtonPress i ButtonRelease. Někdy je nutné po omezenou dobu vyhradit X server pro jediného klienta. Např. některé window managery při interaktivním přesunu nebo změně velikosti okna kreslí průběžně obrys okna a teprve nakonec překreslí celé okno. Aby nebylo třeba pamatovat si původní hodnoty pixelů překrytých obrysem, kreslí se obrys pomocí kombinace s původní hodnotou pixelů operací XOR. Z vlastností XOR plyne, že opětovné překreslení obnoví původní hodnotu, a tedy obrys smaže. To ale platí jen tehdy, pokud se během dvou kreslicích operací hodnoty pixelů nezmění. Proto před zahájením interaktivní manipulace s oknem window manager zavolá XGrabServer a po definitivním umístění okna uvolní grab pomocíXUngrabServer. Mezi těmito dvěma voláními X server nezpracovává žádné požadavky ostatních klientů. Obecně platí, že jakýkoliv grab by se měl používat, pouze když je to nezbytně nutné.
Události z klávesnice reprezentované strukturou XKeyEvent obsahují kód stisknuté nebo uvolněné klávesy (XKeyEvent.keycode) a stav modifikátorů, tj. kláves jako Shift a Ctrl, v položce XKeyEvent.state. Mapování mezi fyzickými klávesami a hodnotami keycode je pevně dáno X serverem. Klient obvykle událost předá funkci XLookupString. Ta převede keycode na keysym a na řetězec. Keysym je symbolická konstanta reprezentující klávesu, např. XK_a
nebo XK_Page_Up. Tabulka pro mapování z keycode na keysym je definovaná globálně v X serveru. Dá se měnit utilitami xmodmap nebo setxkbmap. Druhá možnost využívá rozšíření XKB (X Keyboard Extension), které poskytuje větší flexibilitu při definování rozložení klávesnice než standardní obsluha klávesnice v X. Protože převodní tabulka je v serveru a převod na keysym dělá klient, rozešle server při změně rozložení klávesnice událost MappingNotify, na niž klienti zareagují aktualizací své kopie klávesové mapy pomocí XRefreshKeyboardMapping. Standardní mapování z keysym na řetězec přiřazuje klávesám generujícím platné znaky jednoznakové řetězce. Ostatním klávesám (šipky, funkční klávesy) přiřazuje prázdný řetězec. Funkce
XRebindKeysym umožňuje změnit toto mapování lokálně v rámci jednoho klienta.Některé jazyky mají schéma psaní znaků složitější než 1 klávesa = 1 znak. Jeden znak může být vytvořen posloupností několika stisků kláves. Takové skládání kláves řídí vstupní metody (input methods). Např. pro některé asijské jazyky používající písmo s velkým počtem znaků uživatel napíše název znaku v transkripci do latinky. Vstupní metoda mu přitom ve speciálním okně zobrazuje aktuální stav a umožňuje editaci. Nakonec se celý dlouhý název znaku překonvertuje na jediný znak a pošle se aplikaci. Dalším příkladem je i čeština. Obvyklá česká klávesnice používá mrtvé klávesy pro čárku a háček. Stisk mrtvé klávesy následovaný písmenem vygeneruje jediné čárkované nebo háčkované písmeno. O složení dvou kláves do jednoho písmene se opět postará vstupní metoda. Zapojení vstupních metod do komunikace mezi X serverem a klienty je zobrazeno na obr. 1. Klient A má jednoduchou vstupní metodu implementovanou jako součást Xlib. To může být případ českých mrtvých kláves. Klient B používá komplexní vstupní metodu fungující jako samostatný program napojený na server i na klienta. Pro konverze znaků nebo třeba hledání znaků či slov ve slovníku používá vstupní metoda externí language engine.
Obr. 1: Vstupní metody
Na straně klienta je potřeba nastavit správné locale (setlocale(LC_ALL, "")), otestovat, zda je podporováno v Xlib (XSupportsLocale) a nastavit případné modifikátory (XSetLocaleModifiers) podle environmentové proměnné XMODIFIERS. Pak už je možné otevřít vstupní metodu (XOpenIM) a vytvořit vstupní kontext (XCreateIC). Během zpracování událostí se každá událost přijatá funkcí XNextEvent nejprve předá vstupní metodě pomocí XFilterEvent a podle návratové hodnoty klient buď událost ignoruje, nebo ji dále zpracovává, např. pomocí XLookupString.
Komunikace mezi programy
X Window System obsahuje mechanismy pro komunikaci mezi klienty. Její pravidla definuje dokument Inter-Client Communication Conventions Manual (ICCCM). Do tohoto manuálu je vhodné se vždy při použití některého komunikačního mechanismu podívat na detaily jeho fungování. Komunikace je zprostředkovaná serverem, takže klienti nemusí mít mezi sebou přímé spojení. Základním prostředkem pro předávání dat jsou properties, což jsou jakési balíčky dat připojené k oknům. Každé okno jich může nést několik. Property je identifikovaná jménem a číslem okna. Jsou v ní uložena data a jméno typu dat. Každý klient může měnit libovolnou property. Server při změně property vygeneruje událost PropertyNotify.
Jako jména properties a typů se používají atomy. Atom je jednoznačný číselný identifikátor. Zásadním problémem takových identifikátorů jsou kolize, kdy dva logicky různé identifikátory mají stejnou hodnotu. Pravděpodobnost kolizí se sníží používáním delších identifikátorů, např. textových řetězců. Používání textových identifikátorů by však zvětšovalo objem dat přenášených mezi serverem a klientem. Řešení použité v X funguje tak, že každý atom má jméno. Klient pomocí XInternAtom pošle serveru řetězec a server mu vrátí atom. Pokud je funkce XInternAtom volána několikrát se stejným řetězcem, vrátí stejný atom. Pro různé řetězce vrací různé atomy.
Funkce XSetWMProperties slouží pro nastavení standardní sady properties, jimiž klient předává informace window manageru. Klient určuje titulek okna a ikony, obsah příkazového řádku použitelného pro restart klienta a jméno počítače, kde klient běží, dále ikonu a její počáteční pozici, omezení velikosti okna, počáteční stav okna (normální/ikona), model pro přidělování fokusu klávesnice a identifikátor skupiny top-level oken příslušných ke stejné aplikaci. Nastavením identifikátorů protokolů do property WM_PROTOCOLS říká klient, že chce od window manageru dostávat informace ve formě událostí ClientMessage. Protokol WM_TAKE_FOCUS slouží ke sdělení, kdy si aplikace smí nastavit focus voláním XSetInputFocus. Pomocí protokolu WM_SAVE_YOURSELFsděluje session manager nebo window manager klientovi, aby uložil svůj stav, neboť bude ukončen. Když klient používá protokol WM_DELETE_WINDOW a uživatel zkusí zavřít okno prostřednictvím window manageru (obvykle kliknutím na zavírací tlačítko v rámečku okna), předá window manager žádost o zavření okna klientovi. Klient si může vyžádat potvrzení od uživatele nebo uložit data a následně buď okno zavřít, nebo žádost ignorovat. Klient nepoužívající WM_DELETE_WINDOW bude odpojen od X serveru, jakmile uživatel prostřednictvím window manageru zavře některé jeho top-level okno.
Pro přenášení dat mezi klienty se používají výběry (selections). V Xlib se s nimi pracuje velmi podobně jako v GTK+. Schéma fungování výběrů je na obr. 2. Všimněte si, že vypadá téměř stejně jako obr. 1 v 5. dílu tohoto seriálu.
Obr. 2: Komunikace pomocí výběrů
Výběrů lze definovat libovolně mnoho, ale obvykle se používá XA_PRIMARY. Když si uživatel vybere nějaká data, klient si přivlastní výběr funkcí XSetSelectionOwner. Předchozí vlastník výběru je o tom informován událostí SelectionClear. Když chce uživatel vložit data do nějakého okna, klient vlastnící toto okno zavolá XConvertSelection. V parametrech zadá property, do níž má vlastník data vložit, a požadovaný typ dat (target). Vlastník výběru dostane událost XSelectionRequest a v reakci na ni uloží data do property pomocí XChangeProperty a funkcí XSendEvent pošle příjemci dat událost SelectionNotify. Příjemce si pak data vyzvedne voláním XGetWindowProperty.
Konfigurace aplikací
Pro konfiguraci aplikací poskytuje Xlib funkce pro práci s tzv. resource databází. Jednotlivé konfigurační hodnoty (resources) se při startu programu čtou z několika míst. V pořadí podle priority od nejmenší po největší jsou to postupně:
- Soubor Classname v adresáři/usr/X11R6/lib/X11/app-defaults. Zde Classname je označení třídy aplikace, které je obvykle stejné jako jméno programu s prvním nebo několika prvními písmeny změněnými z malých na velká, např. XTerm pro program xterm.
- Soubor Classname v adresářích zadaných environmentovými proměnnými XUSERFILESEARCHPATH aXAPPLRESDIR.
- Properties RESOURCE_MANAGER root okna obrazovky 0 a SCREEN_RESOURCES root oken jednotlivých obrazovek. Obsah těchto properties se obvykle nastavuje programem xrdb při startu uživatelského prostředí X. Pokud nejsou nastaveny, použije se soubor .Xdefaults v domovském adresáři.
- Soubor zadaný environmentovou proměnou XENVIRONMENT, když není nastavená, použije se soubor .Xdefaults-hostname v domovském adresáři.
- Argumenty z příkazové řádky
Na začátku programu je nutné nejprve volat inicializační funkci XrmInitialize. Jednotlivé části resource databáze se čtou pomocí funkcí XrmParseCommand, XrmGetFileDatabase aXrmGetStringDatabase. Nakonec se vše spojí do jediné databáze voláním XrmMergeDatabases.
Jednotlivé položky resource databáze jsou řádky tvaru jméno: hodnota. Hodnota je řetězec, do kterého je možné pomocí escape sekvencí vkládat libovolné znaky, např. „\n“ je konec řádku a „\123“ je znak s oktalovým číslem 123. Zajímavější je jméno položky. Skládá se z komponent oddělených tečkami. Každá komponenta je buď jméno třídy začínající velkým písmenem, nebo jméno instance začínající malým písmenem. V aplikacích používajících X Toolkit odpovídají jednotlivé komponenty úrovním stromu widgetů. V programech postavených přímo nad Xlib je možné stromovou strukturu resource databáze použít pro logické uspořádání konfiguračních hodnot. Místo libovolné komponenty jména (kromě poslední) se dá použít wildcard otazník. Posloupnost libovolně mnoha komponent lze nahradit wildcardem hvězdička. Příklad úseku resource databáze:
Fig.exportLanguage: eps .xf86cfg.geometry: 320x400 XCalc*hp.button1.horizDistance: 4 *Text.?.background: rgb:29/44/94
Pro hledání v databázi slouží funkce XrmGetResource. Jako parametry dostane dvě kompletní jména položek bez wildcardů. Jedno jméno je posloupnost jmen tříd, ve druhém jsou pouze jména instancí. Funkce najde v databázi nejlépe odpovídající položku a vrátí ji. Pokud se při vytváření databáze narazí na několik položek s přesně stejným jménem, ponechá se pouze poslední z nich. Jestliže zadanému klíči odpovídá několik položek, porovnávají se po komponentách zleva doprava a vždy se ponechají jen položky s největší prioritou. Porovnávání pokračuje, dokud nezůstane jediná položka. Pravidla pro priority zajišťují, že konkrétněji specifikovaná položka má přednost před obecnou. Položka, která obsahuje aktuálně testovanou komponentu (topLevel.quit.background, topLevel.Command.background a topLevel.?.background), má přednost před položkou, která komponentu přeskakuje pomocí hvězdičky (topLevelbackground). Položka se jménem instance (quit.background) má přednost před položkou obsahující jméno třídy (Command.background) a ta má přednost před wildcardem otazník (?.background). Položka, před níž je tečka (box.background), má přednost před položkou, kterou předchází hvězdička (box*Background).
Window manager
Window manager je normální X klient, ale má speciální roli – spravuje top-level okna ostatních klientů. Pro tento účel mu Xlib poskytuje několik speciálních nástrojů. První je substructure redirection. Window manager si v masce událostí root okna nastaví SubstructureRedirectMask. Jestliže jiný klient zkusí namapovat svoje top-level okno nebo změnit jeho pozici, velikost, šířku okraje či stacking order, tato operace se nepovede a místo toho se window manageru pošle událost CirculateRequest, ConfigureRequest, nebo MapRequest obsahující popis požadavku klienta. Window manager následně požadavek provede nebo zamítne. Některá okna – např. popup menu a tooltipy – nejsou řízena window managerem. Pro taková okna je třeba nastavit atribut override_redirect.
Většina window managerů kreslí kolem jednotlivých top-level oken rámečky. Pro implementaci těchto rámečků se využívá reparenting oken. Když window manager dostane událost MapRequest, vytvoří rámeček – nové top-level okno, o trochu větší, než je okno aplikace. Původní top-level okno aplikace vloží do rámečku, tj. pomocí funkce XReparentWindow změní jeho rodiče z kořenového okna na rámeček. Obě okna pak namapuje.
Tím, že window manager vytvoří nová rodičovská okna pro top-level okna aplikací a občas některá okna odmapuje a místo nich zobrazí ikony, by došlo při ukončení window manageru ke zrušení všech takových oken. Proto window manager ukládá všechna spravovaná okna do save-set volánímXAddToSaveSet. Při ukončení window manageru X server automaticky všem oknům ze save-set změní rodiče zpět na root okno a okna namapuje.