Hlavní navigace

Programování pro X Window System: jde to i bez toolkitu

Daniel Novotný 19. 2. 2001

... aneb jak na rychlé a paměťově nenáročné grafické aplikace. Ponořme se dnes do tajů skrytých za jedním velkým "X".

Pokud chce člověk programovat v systému X Window v jazycích C či C++, má k dispozici ohromné množství různých „toolkitů“ – tj. knihoven poskytujících tzv. „widgety“ neboli tlačítka, posuvníky, editační boxy pro vstup textu a další podobné věci. Jmenujme třeba knihovny Athena, GTK+ nebo Motif. Pokud chcete ale pouze programovat grafické aplikace v systému Linux a knihovna SVGALib vám připadá jako příliš nesystémové řešení (pokud se program pod ní napsaný „sekne“, může „zamrznout“ celý systém; dále SVGALib je specifikum Linuxu a programy nepůjdou přeložit pod jiným UNIX-like systémem), můžete chtít využít nízkoúrovňové grafické služby X Window Systému. Jestliže toužíte například napsat počítačovou hru nebo animaci, mohou pro vás být toolkity zbytečnou vrstvou navíc a můžete ocenit možnost použít přímo funkce typu „nakresli pixel“, „nakresli úsečku“, „věnuj pozornost klávesnici“, „reaguj na pohyb myši“ apod. bez prostředníka zvaného toolkit.

Pokud vám moje myšlenkové postupy v předchozím odstavci přišly povědomé, možná oceníte tento článek, který je úvodem do programování grafiku používajících aplikací s pomocí X Window Systemu a jeho knihovny XLib.

Rozhodl jsem se vás do problematiky uvést tím způsobem, že napíšu malý prográmek demonstrující použití XLibky v počítačové grafice a společně tento prográmek rozebereme. Ukázkový program je napsán v jazyce C++, ale nepoužívá žádné objektové vlastnosti tohoto jazyka – pouze mi připadala přehlednější možnost definovat proměnné kdekoli v kódu programu a ne jen na začátku funkcí či mimo funkce. Také se mi líbí „pluskové“ inline funkce, ale ty nejdou v našem příkladě použít.

Náš program bude velmi jednoduchá animace – kreslení náhodně umístěných čar náhodnou barvou přerušitelné stiskem libovolné klávesy. Najdete ho na konci tohoto článku. Přes jednoduchost algoritmu vypadá výsledná animace docela hezky. Program je možno přeložit a spustit mimo Linuxu i pod jinými UNIXovými systémy (ekvivalentní program využívající SVGALib místo XLib by fungoval pouze pod Linuxem; můj program byl zkompilován a spuštěn i na stroji Sun s operačním systémem SunOS a na stroji SGI Indy s Irixem).

Po nezbytném úvodu přejděme „k věci“:

Program pod X funguje díky spolupráci čtyř systémů:

  1. X Serveru, který se stará o „nízkoúrovňové“ služby jako komunikaci s grafickou kartou či s klávesnicí a myší. Plní příkazy naší aplikace a zpět jí posílá informace o událostech (events) v systému
  2. tzv. „toolkitu“ což je knihovna obsahující prvky uživatelského rozhraní
  3. Window Manageru, který se stará o věci, jako přesouvání oken, dekorace oken apod.
  4. a konečně vlastní aplikace, která využívá služeb X Serveru a může (ale nemusí) využívat služeb některého toolkitu a komunikovat s Window Managerem

V našem případě se budeme zajímat pouze o body (1) a (4) – přítomnost Window Manageru nás v podstatě nezajímá a žádný toolkit používat nebudeme.
Implementace mechanismu komunikace mezi aplikací a XServerem je „zapouzdřena“ knihovnou XLib a je pro nás v podstatě transparentní. Nebudeme se jí proto zabývat – pouze uvedeme, že umožňuje, aby náš program a XServer běžely každý na jiném stroji v síti.

Pokud chce aplikace začít využívat služeb XServeru, musí se s ním kontaktovat – to se zařídí funkcí XOpenDisplay s prototypem

Display *XOpenDisplay(char * display_name);

pokud místo display_name uvedeme NULLový ukazatel, vezme se hodnota proměnné prostředí DISPLAY. To je to, co nyní chceme, proto první „Xový“ příkaz naší aplikace zní

Display *dpy = XOpenDisplay(0);

Funkce vrátí ukazatel na strukturu Display, který budeme nadále při komunikaci s XServerem používat. Nebudeme se dívat „dovnitř“ této struktury – budeme s ukazatelem na ní tedy zacházet podobně jako s ukazatelem na strukturu typu FILE v I/O operacích. Pokud je dpy nulový ukazatel, znamená to, že se pokus o kontakt s XServerem nepodařil. Většinou to znamená, že uživatel spustil program na textové konzoli místo pod X. Můžeme na to zareagovat v podstatě jakkoli – často se používá hlášení chyby a potom exit(), ale to nemusí být pravidlem. Například některé verze EMACSu umí využívat služeb X, ale pokud je spustíme mimo X, editoru to nevadí – funguje v textovém režimu. Já jsem se rozhodl vypsat hlášku parodující jeden nejmenovaný operační a zároveň okenní systém a potom program ukončit s indikací chyby v návratové hodnotě.

if(!dpy) {
  fprintf(stderr,"This program requires X Windows.\n");
  exit(1);
}

Dále potřebujeme vytvořit okno, do kterého budeme kreslit. K tomu slouží funkce XCreateWindow() a XCreateSimple­Window(). Ukážeme si prototyp druhé z nich:

Window XCreateSimpleWindow(Display * display, Window parent,
           int x, int y,
           unsigned int width, unsigned int height,
           unsigned int border_width,
                   unsigned long border,
           unsigned long background);

Argumenty jsou většinou „samo-vysvětlující“, případně se můžete podívat do manuálové stránky. Poslední dva parametry jsou barvy – při alokaci barvy dostanete číslo typu unsigned long, kterým se potom na barvu odkazujete. My potřebujeme uvést číslo barvy černé, proto ho získáme k tomu uzpůsobenou funkcí BlackPixel():

int blackColor = BlackPixel(dpy, DefaultScreen(dpy));

Nyní již můžeme bez obav zavolat funkci XCreateSimple­Window():

Window w = XCreateSimpleWindow(dpy, DefaultRootWindow(dpy),
                   0, 0,  XSIZE, YSIZE, 0,
                   blackColor, blackColor);

Kde XSIZE a YSIZE jsou konstanty určující velikost okna – jsou #definovány na začátku našeho programu a můžete si je zvolit takřka libovolně.
Naše okno ještě nemá určen titulek. To učiníme trochu složitější funkcí XChangeProperty(). Zájemce o studium dalších možností této funkce odkazuji na manuálové stránky – bylo by zbytečné je zde přepisovat.

const char * titulek = "Jde to i bez toolkitu!";
XChangeProperty(dpy, // display
          w,  // ID okna
          XA_WM_NAME, // atom vlastnosti
          XA_STRING, // typ vlastnosti
          8, // je osmibitová
          PropModeReplace, // budeme nahrazovat (Replace)
          (const unsigned char *) titulek, //data vlastnosti
          strlen(titulek) // počet elementů ve vlastnosti
         );

Vidíte, proč jsem použil C++ místo C ? Takhle můžu proměnnou „titulek“ deklarovat až v místě její potřeby. Tuto vlastnost C++ použijeme v našem kódu i jinde.

Okno zatím není na obrazovce – to musíme zařídit funkcí XMapWindow(), která okno zobrazí. Je zde menší problémek v tom, že okno se na obrazovce neobjeví hned – proto musíme počkat, až se objeví. K tomuto účelu nám poslouží mechanismus Xových událostí (events). Všechno se točí okolo datového typu XEvent – ten je unionem mnoha různých struktur. Všechny tyto struktury mají společnou pouze první položku – a to položku type typu int, která určuje typ události. Typ XEvent je deklarovaný v souboru /usr/include/X11/Xlib­.h . My pouze X Serveru řekneme, které události nás zajímají a tyto pak budeme dostávat. To se zadává třetím parametrem funkce XSelectInput() – jsou to bitové masky všech možných událostí logicky sečtené dohromady. První dva parametry této funkce jsou display a okno. Ten třetí parametr může být bitovým ORem věcí jako KeyPressMask, EnterWindowMask, LeaveWindowMask, PointerMotionMask atd. , které jsou #definovány v souboru /usr/include/X11/X­.h. V našem případě si „objednáme“, že chceme přijímat událost objevení okna, což zařídí StructureNotifyMask ve třetím parametru funkce XSelectInput() .

XSelectInput(dpy, w, StructureNotifyMask);

Jestliže jsme již zařídili, že nám systém řekne, kdy se okno skutečně zobrazí, můžeme konečně zobrazení okna přikázat.

XMapWindow(dpy, w);

Potom budeme ve smyčce čekat, dokud se okno neobjeví na obrazovce. K tomu nám poslouží funkce XNextEvent, kterým vybíráme následující událost z fronty událostí. Události budeme vybírat tak dlouho, dokud nenarazíme na událost typu MapNotify. To, že jsme na ní narazili, zjistíme pomocí složky type unionu XEvent.

XEvent e;

do {
    XNextEvent(dpy, &e);
}
while( e.type != MapNotify);

Tak – okno je na obrazovce, můžeme začít kreslit. Napřed vytvoříme „grafický kontext“ – věc, na kterou se budeme odkazovat při všech „kreslících“ funkcích. Parametry této funkce – viz příslušná manuálová stránka.

GC gc = XCreateGC(dpy, w, 0, 0);

Abychom mohli rozumně kreslit, musíme si alokovat nějaké barvičky. Toho se týká struktura XColor, deklarovaná takto:

typedef struct {
            //identifikační číslo dané barvy v paletě
            unsigned long pixel;
            //barevné složky RGB 0 až 0xffff
        unsigned short red, green, blue;
            //tyto dva chary nás už nezajímají
        char flags;
            char pad;
        } XColor;

V našem programu nadefinujeme pole s těmito strukturami jako prvky. Jak je vidět z deklarace typu, na prvním místě je číslo barvy, které chceme naší barvě přidělit (ale XServer nám to může změnit – struktura je vstupní i výstupní).
Dále jsou RGB složky, parametry za nimi nás příliš nezajímají a mohou zůstat nulové.

XColor barvy[]= { {1,0xffff,0,0},  //červená
        {2,0,0xffff,0},  //zelená
        {3,0,0,0xffff},  //modrá
        {4,0xffff,0xffff,0}, //žlutá
        {5,0,0xffff,0xffff}, //jasně modrozelená
        {6,0xffff,0xffff,0xffff}, //bílá
          };
const int barev=sizeof(barvy)/sizeof(XColor);

Samozřejmě že hodnoty mohou být jiné než 0 a 0×ffff, klidně si přidejte další barvy.
Barvy nyní naalokujeme funkcí XAllocColor() a to samozřejmě v cyklu. Ve třetím parametru předáváme ukazatel na strukturu XColor, který bude vstupní (RGB hodnoty) i výstupní (systém může změnit číslo barvy, v tom případě jej ve struktuře přepíše).

for(int i=0;i<barev;i++)
        XAllocColor(dpy,DefaultColormap(dpy,DefaultScreen(dpy)),
                barvy+i );

Místo &(barvy[i]) jsem použil ekvivalentní, kratší zápis barvy+i , který snad každý Céčkař pochopí.

Protože budeme chtít program ukončit stiskem libovolné klávesy, řekneme XServeru, že nás zajímají události týkající se stisknutí klávesy. Použijeme funkci XSelectInput(), kterou jste zde již jednou viděli.

XSelectInput(dpy, w, KeyPressMask);

Teď přichází hlavní smyčka programu – inicializace máme již za sebou a můžeme se pustit do vlastního kreslení. Budeme v cyklu kreslit náhodné čáry náhodnou barvou, dokud nepřijde událost typu KeyPress.

Napřed funkcí XSetForeground() nastavíme číslo barvy, kterou budeme kreslit. Toto číslo je atribut unsigned long pixel struktury XColor. Vybereme proto náhodný prvek našeho pole a jeho atribut pixel předáme:

XSetForeground(dpy, gc,barvy[random()% barev].pixel  );

Pokud nás v podstatě nezajímá, jaká barva to bude a chceme pouze náhodnou barvu ze všech barev v systému, můžeme to zkusit i jinak. Po zkompilování a spuštění programu zkuste odkomentovat následující řádek, který se snaží vybrat náhodnou barvu ze všech dostupných. Alokace barev je však důležitá a většinou nás v animacích, hrách apod. zajímá, která přesně barva to je, proto jsem v našem příkladě alokaci barev nemohl nepoužít. Ovšem jde to i takto:

XSetForeground(dpy,gc,
        random()%počet_barev_vašeho_grafického_režimu);

Kde za pseudokonstantu počet_barev_va­šeho_grafické­ho_režimu doplňte, kolik máte v systému barev. Nakreslíme poté náhodnou čáru funkcí XDrawLine, jejíž prototyp je následující:

XDrawLine(Display * display,Drawable d,GC gc,
        int x1,int y1,int x2,int y2);

„Drawable“ je například okno, ostatní parametry jsou jasné. Konkrétní volání této funkce v naší animaci vypadá takto:

XDrawLine(dpy, w, gc,
      random()%XSIZE, random()%YSIZE ,
      random()%XSIZE , random()%YSIZE
     );

To, co jsme nakreslili, se v okně zobrazí teprve poté, co to „spláchneme“ funkcí XFlush():

XFlush(dpy);

Nyní přicházíme k závěru – k ukončující podmínce našeho do { } while kreslícího cyklu. Je to funkce s poměrně dlouhým názvem XCheckTypedWin­dowEvent(). Narozdíl od XNextEvent() neblokuje, ale pokud daná událost nenastala, vrátí nulu čili false. Cyklus tedy vypadá takto:

do
{
      XSetForeground(...);
      XDrawLine(...);
      XFlush(dpy);
}
while(! XCheckTypedWindowEvent(dpy,w,KeyPress,&e) );

kde &e je ukazatel na strukturu XEvent e definovanou výše. Pokud bychom chtěli se stisknutou klávesou nějak dále pracovat, vrátí nám ji XServer v položce e.xkey.keycode . Není to však ASCII ani jiná rozumná hodnota – na něco srozumitelnějšího nám jí překonvertuje funkce XKeycodeToKeysym(), která v programu není použita. Ta vrátí hodnotu typu KeySym, což je věc definovaná v /usr/include/X11/ke­ysymdef.h . Podívejte se tam a uvidíte.

To je vše, přátelé – nakonec pouze pro zajímavost zobrazíme keycode, který jsme dostali a vrátíme operačnímu systému nulu jako příznak normálního a bezchybného ukončení.

Zájemci si mohou kompletní zdrojový kód naší ukázkové animace stáhnout zde. Přeložíte jej příkazem

c++ animace.cc -L/usr/X11R6/lib -lX11

za předpokladu, že jste kód uložili do souboru animace.cc a že jsou Xové knihovny na vašem systému uloženy v adresáři /usr/X11R6/lib).

Našli jste v článku chybu?

4. 4. 2001 18:46

Reggae (neregistrovaný)

Cenové náočosti bych se nebál. Ve 2. čtrvtletí 2001 uvede Borland Kylix Open Edition, šířenou pod GPL licencí, tato verze bude zdarma včetně zdrojáků. Progamy sestavené touto verzí budou muzet ale být šířreny opět pod GPL licencí, tedy i se zdrojovými kódy ať už za úplatu nebo ne.
Zdroj: http://www.borland.cz/t_kylix_uvedeni.html


14. 3. 2001 19:47

Daniel Novotny (neregistrovaný)

>dostal jsem chut napsat
> si nejakou hru :o)
SKVELY! tohle bylo jednim ze skrytych ucelu meho
clanku: pod Linuxem je stale jeste mene gamesek
nez pod Woknama ci dosem (i kdyz FreeCiv rulez
a Koules taky rulez)
Takze napsani dobre hry pod Linuxem nam pomuze
- pritahne "par~any" do nasich r~ad.








DigiZone.cz: Česká televize mění schéma ČT :D

Česká televize mění schéma ČT :D

Vitalia.cz: Říká amoleta - a myslí palačinka

Říká amoleta - a myslí palačinka

Podnikatel.cz: Přehledná titulka, průvodci, responzivita

Přehledná titulka, průvodci, responzivita

Podnikatel.cz: Vládu obejde, kvůli EET rovnou do sněmovny

Vládu obejde, kvůli EET rovnou do sněmovny

Lupa.cz: Slevové šílenství je tu. Kde nakoupit na Black Friday?

Slevové šílenství je tu. Kde nakoupit na Black Friday?

Lupa.cz: Google měl výpadek, nejel Gmail ani YouTube

Google měl výpadek, nejel Gmail ani YouTube

DigiZone.cz: Sony KD-55XD8005 s Android 6.0

Sony KD-55XD8005 s Android 6.0

Měšec.cz: Kdy vám stát dá na stěhování 50 000 Kč?

Kdy vám stát dá na stěhování 50 000 Kč?

Lupa.cz: UX přestává pro firmy být magie

UX přestává pro firmy být magie

Vitalia.cz: To není kašel! Správná diagnóza zachrání život

To není kašel! Správná diagnóza zachrání život

120na80.cz: Pánové, pečujte o svoje přirození a prostatu

Pánové, pečujte o svoje přirození a prostatu

120na80.cz: Jak oddálit Alzheimera?

Jak oddálit Alzheimera?

DigiZone.cz: NG natáčí v Praze seriál o Einsteinovi

NG natáčí v Praze seriál o Einsteinovi

Lupa.cz: Propustili je z Avastu, už po nich sahá ESET

Propustili je z Avastu, už po nich sahá ESET

DigiZone.cz: Recenze Westworld: zavraždit a...

Recenze Westworld: zavraždit a...

Lupa.cz: Co se dá měřit přes Internet věcí

Co se dá měřit přes Internet věcí

Měšec.cz: Zdravotní a sociální pojištění 2017: Připlatíte

Zdravotní a sociální pojištění 2017: Připlatíte

Podnikatel.cz: 1. den EET? Problémy s pokladnami

1. den EET? Problémy s pokladnami

Vitalia.cz: Láska na vozíku: Přitažliví jsme pro tzv. pečovatelky

Láska na vozíku: Přitažliví jsme pro tzv. pečovatelky

Měšec.cz: Finančním poradcům hrozí vracení provizí

Finančním poradcům hrozí vracení provizí