Xlib - základní aplikace s jedním oknem

20.11.2020

V předchozích jsme si ukázali jak vytvořit jednoduchou aplikaci v GDK. Pro ty kteří chtějí jít ještě "níž", ukážeme si základní okenní aplikaci napsanou v Xlib, což je "low-level" knihovna zabalující komunikaci s X11 serverem a vytvářet aplikace s grafickým uživatelským rozhraním.

Samozřejmě že v současné době v naprosté většině případů použijeme pro vytvoření nové GUI aplikace nějakou komplexní knihovnu/framework, jako je GTK, wxWidgets nebo Qt. Nicméně pro nějaké malé utility, pluginy pro panely nástrojů apod. kde "odlehčenost" může mít význam, je stále Xlib variantou k uvážení. Navíc stále jsou udržované aplikace napsané v Xlib a pokud hledáme inspiraci v jejich zdrojovém kódu, alespoň základní znalost Xlib se jistě hodí. Kromě toho mnozí programátoři rádi vidí pod povrch jak věci fungují.

Nejprve se musíme připojit k X serveru. Na to použijeme funkci XOpenDisplay. Pokud neurčíme parametr, resp. zadáme NULL, získáme připojení k výchozímu serveru. Poté co získáme číslo výchozí obrazovky pomocí funkce XDefaultScreen, popř. makrem DefaultScreen, můžeme vytvořit okno:

Window window = XCreateSimpleWindow(display, RootWindow(display, screen),
	10, 10, 800, 600, 1,
	BlackPixel(display, screen), WhitePixel(display, screen));

Jako parametry zde zadáváme získané připojení k X serveru, rodičovské okno (pro "top-level" okna použijeme hlavní kořenové okno), dále požadované souřadnice, tj. pozice a rozměry a barvu okraje a pozadí.

Abychom byli schopni následně zachytit a reagovat na vstupní události klávesnice a myši, zavoláme funkci XSelectInput, v jejímž 3. parametru zkombinujeme požadované masky, mezi kterými musí být KeyPressMask a ButtonPressMask. V ukázkovém kódu je dále uvedena hodnota ExposureMask, která zajistí že dostaneme událost požadavku na překreslení okna, kterou sice v našem základním programu nemáme implementovanou, ale v praxi ji samozřejmě budeme využívat, a tom někdy příště.

Aby se okno zobrazilo, namapujeme ho na server přes dříve získané připojení (Display*), a nastavíme jeho název.

Pak už zbývá implementovat smyčku/cyklus ve které budeme přijímat události týkající se okna a podle potřeby na některé reagovat. V každém cyklu vždy nejprve zavoláme funkci XNextEvent, která čeká než se ve frontě objeví nějaká událost a tuto zkopíruje do struktury typu XEvent. V položce type této struktury máme typ události. Další položky pak obsahují data specifická pro konkrétní typ události, jako je např. tlačítko myši nebo klávesa která byla stisknuta/uvolněna.

V našem případě budeme chtít při stisknutí jakékoliv klávesy nebo kliknutí myší okno zrušit a ukončit aplikaci. Proto při detekci zmíněných událostí vyskočíme z cyklu, po kterém před ukončením funkce main zrušíme okno funkcí XDestroyWindow a uvolníme získané spojení na X server funkcí XCloseDisplay.

Pokud používáme správce oken s plovoucími a dekorovanými okny, tj. která mají titulkový pruh s tlačítky pro zavření, minimalizaci (a popř. další akce), budeme také chtít reagovat na požadavek na zrušení okna tímto tlačítkem. Proto použijeme funkci XSetWMProtocols, ve které zadáme atom WM_DELETE_WINDOW (získaný funkcí XInternAtom). Poté můžeme v cyklu zachytit také událost ClientMessage, a data události otestovat na WM_DELETE_WINDOW, a při rovnosti také ukončit cyklus na následně aplikaci.

Na závěr celý výpis ukázkového programu, který lze sestavit překladačem GCC následujícím příkazem:

gcc xlib-okno.c -lX11 -oxlib-okno
#include <stdlib.h>
#include <stdio.h>
#include <X11/Xlib.h>

int main(int argc, char const *argv[])
{
	Display* display = XOpenDisplay(NULL);
	if (NULL == display)
	{
		printf("Chyba XOpenDisplay\n");
		return EXIT_FAILURE;
	}
	int screen = DefaultScreen(display);
	Window window = XCreateSimpleWindow(display, RootWindow(display, screen),
		10, 10, 800, 600, 1,
		BlackPixel(display, screen), WhitePixel(display, screen));
	XSelectInput(display, window, ExposureMask | KeyPressMask | ButtonPressMask);
	XMapWindow(display, window);
	XStoreName(display, window, "X11 window");
	Atom WM_DELETE_WINDOW = XInternAtom(display, "WM_DELETE_WINDOW", False); 
	XSetWMProtocols(display, window, &WM_DELETE_WINDOW, 1);  
	XEvent event; 
	while(1)
	{
		XNextEvent(display, &event);
		if (event.type == KeyPress)
			break;
		if (event.type == ButtonPress)
			break;
		if ((event.type == ClientMessage) && 
        ((unsigned int)(event.xclient.data.l[0]) == WM_DELETE_WINDOW))
			break;
	}
	XDestroyWindow(display, window);
	XCloseDisplay(display);
	return EXIT_SUCCESS;
}