Начнем изучение с программы, рисующей на экране строку, например "First, example!". Для начала рассмотрим основные шаги, необходимые для работы в X Window. Этот пример можно в дальнейшем можно использовать как шаблон для ваших собственных более сложных программ.
Программа должна выполнить следующие действия:
XOpenDisplay()
,
в случае неудачи выход с сообщением об ошибке.XCreateSimpleWindow()
.XSelectInput()
.XMapWindow()
.Expose
, также здесь
описываются действия программы по умолчанию.KeyPress
, ButtonPress
,
или другие.#include <X11/Xlib.h> #include <X11/Xutil.h> #include <X11/Xos.h> #include <stdio.h> #include <string.h> #define X 0 #define Y 0 #define WIDTH 200 #define HEIGHT 200 #define WIDTH_MIN 50 #define HEIGHT_MIN 50 #define BORDER_WIDTH 5 #define TITLE "Example" #define ICON_TITLE "Example" #define PRG_CLASS "Example" /* * SetWindowManagerHints - функция, которая передает информацию о * свойствах программы менеджеру окон. */ static void SetWindowManagerHints ( Display * display, /*Указатель на структуру Display */ char * PClass, /*Класс программы */ char * argv[], /*Аргументы программы */ int argc, /*Число аргументов */ Window window, /*Идентификатор окна */ int x, /*Координаты левого верхнего */ int y, /*угла окна */ int win_wdt, /*Ширина окна */ int win_hgt, /*Высота окна */ int win_wdt_min, /*Минимальная ширина окна */ int win_hgt_min, /*Минимальная высота окна */ char * ptrTitle, /*Заголовок окна */ char * ptrITitle, /*Заголовок пиктограммы окна */ Pixmap pixmap /*Рисунок пиктограммы */ ) { XSizeHints size_hints; /*Рекомендации о размерах окна*/ XWMHints wm_hints; XClassHint class_hint; XTextProperty windowname, iconname; if ( !XStringListToTextProperty (&ptrTitle, 1, &windowname ) || !XStringListToTextProperty (&ptrITitle, 1, &iconname ) ) { puts ( "No memory!\n"); exit ( 1 ); } size_hints.flags = PPosition | PSize | PMinSize; size_hints.min_width = win_wdt_min; size_hints.min_height = win_hgt_min; wm_hints.flags = StateHint | IconPixmapHint | InputHint; wm_hints.initial_state = NormalState; wm_hints.input = True; wm_hints.icon_pixmap= pixmap; class_hint.res_name = argv[0]; class_hint.res_class = PClass; XSetWMProperties ( display, window, &windowname, &iconname, argv, argc, &size_hints, &wm_hints, &class_hint ); } /* main - основная функция программы */ void main(int argc, char *argv[]) { Display * display; /* Указатель на структуру Display */ int ScreenNumber; /* Номер экрана */ GC gc; /* Графический контекст */ XEvent report; Window window; /* Устанавливаем связь с сервером */ if ( ( display = XOpenDisplay ( NULL ) ) == NULL ) { puts ("Can not connect to the X server!\n"); exit ( 1 ); } /* Получаем номер основного экрана */ ScreenNumber = DefaultScreen ( display ); /* Создаем окно */ window = XCreateSimpleWindow ( display, RootWindow ( display, ScreenNumber ), X, Y, WIDTH, HEIGHT, BORDER_WIDTH, BlackPixel ( display, ScreenNumber ), WhitePixel ( display, ScreenNumber ) ); /* Задаем рекомендации для менеджера окон */ SetWindowManagerHints ( display, PRG_CLASS, argv, argc, window, X, Y, WIDTH, HEIGHT, WIDTH_MIN, HEIGHT_MIN, TITLE, ICON_TITLE, 0 ); /* Выбираем события, которые будет обрабатывать программа */ XSelectInput ( display, window, ExposureMask | KeyPressMask ); /* Покажем окно */ XMapWindow ( display, window ); /* Создадим цикл получения и обработки ошибок */ while ( 1 ) { XNextEvent ( display, &report ); switch ( report.type ) { case Expose : /* Запрос на перерисовку */ if ( report.xexpose.count != 0 ) break; gc = XCreateGC ( display, window, 0 , NULL ); XSetForeground ( display, gc, BlackPixel ( display, 0) ); XDrawString ( display, window, gc, 20,50, "First example", strlen ( "First example" ) ); XFreeGC ( display, gc ); XFlush(display); break; case KeyPress : /* Выход нажатием клавиши клавиатуры */ XCloseDisplay ( display ); exit ( 0 ); } } }
Исходный код программы example1.c
Для сборки программы используется команда:
cc -o primer1.out primer1.c -lX11 -L/usr/X11R6/lib
Здесь cc - имя исполняемого файла компилятора. Как правило, это символическая ссылка на реальное имя компилятора (например, gcc). Параметр -o задает имя исполняемого файла; в нашем случае это hello. -lX11 указывает на необходимость подключения библиотеки Xlib, а -L/usr/X11R6/lib определяет путь к ней.
На рис. 1.3 показан внешний вид приложения после его запуска.
Рис. 1.3. Окно приложения xhello в среде KDE
Программа использует ряд функций, предоставляемых библиотекой Xlib: XOpenDisplay(), XCreateSimpleWindow()
и др. Их прототипы, стандартные структуры данных, макросы и константы описаны в следующих основных файлах-модулях: <X11/Xlib.h>, <X11/Xutil.h>, <X11/X.h>, <X11/X11.h>
.
Перейдем к рассмотрению самой программы. Она начинается установлением связи с Х-сервером. Делает это функция XOpenDisplay()
. Ее аргумент определяет
сервер, с которым надо связаться. Если в качестве параметра XOpenDisplay()
получает NULL
, то она открывает доступ к серверу, который задается переменной среды (environment) DISPLAY
. И значение этой переменной, и значение параметра функции имеют следующий формат: host:server.screen
, где host
- имя компьютера, на котором выполняется сервер, server
- номер сервера (обычно это 0), а screen
- это номер экрана.
Функция XOpenDisplay()
возвращает указатель на структуру типа Display
. Это большой набор данных, содержащий информацию о сервере и экранах. Указатель следует запомнить, т.к. он используется в качестве параметра во многих функциях Xlib.
XOpenDisplay()
соединяет программу с X сервером, используя протоколы TCP или DECnet, или же с использованием некоторого локального протокола межпроцессного взаимодействия. Если имя машины и номер дисплея разделяются одним знаком двоеточия (:), то XOpenDisplay()
производит соединение с использованием протокола TCP. Если же имя машины отделено от номера дисплея двойным двоеточием (::), то для соединения используется протокол DECnet. При отсутствии поля имени машины в имени дисплея, то для соединения используется наиболее быстрые из доступных протоколов. Конкретный X сервер может поддерживать как все, так и некоторые из этих протоколов связи. Конкретные реализации Xlib могут дополнительно поддерживать другие протоколы.
Если соединение проведено удачно, XOpenDisplay()
возвращает указатель на структуру Display
, которая определяется в <X11/Xlib.h>
. Если же установить соединение не удалось, то XOpenDisplay() возвращает NULL
. После успешного вызова XOpenDisplay()
клиентской программой могут использоваться все экраны дисплея. Номер экрана возвращается функцией XDefaultScreen()
. Доступ к полям структур Display
и Screen
возможен только посредством использования макроопределений и функций.
После того, как связь с сервером установлена, программа "Hello" определяет номер экрана. Для этого используется функция XDefaultScreen()
, возвращающий номер основного экрана. Переменная nScreenNum
может иметь значение от 0 до величины (ScreenCount(display)-1)
. Макрос XScreenCount()
позволяет получить число экранов, обслуживаемых сервером.
Следующий шаг - создание окна и показ его на дисплее. Для этого программа обращается к фунуции XCreateWindow()
или XCreateSimpleWindow()
. Для простоты мы используем вторую функцию, параметры которой задают характеристики окна.
PrWind = XCreateSimpleWindow ( display, /* указатель на структуру Display */ RootWindow (display, nScreenNum), /* родительское окно, в данном случае, это основное окно программы */ WND_X, WND_Y, /* начальные x и y координаты верхнего левого угла окна программы */ WND_WIDTH, WND_HEIGHT, /* ширина окна и высота окна */ WND_BORDER_WIDTH, /* ширина края окна */ BlackPixel ( display, nScreenNum ), /* цвет переднего плана окна */ WhitePixel ( display, nScreenNum ) /* цвет фона окна */ );
Для задания цветов окна используются функции
XBlackPixel()
и
XWhitePixel()
. Они возвращают значения пикселей, которые считаются на данном дисплее и экране соответствующими "черному" и "белому" цветам. Функция XCreateSimpleWindow()
(XCreateWindow()
) возвращает значение типа Window
. Это целое число, идентифицирующее созданное окно.
Среди параметров функций, создающих окна, есть те, которые определяют положение окна и его размеры. Эти аргументы принимаются во внимание системой X Window. Исключение составляет случай, когда родительским для создаваемого окна является "корневое" окно экрана. В этом случае решение о положение окна и его размерах принимает менеджер окон. Программа может пытаться повлиять на решение менеджера окон, сообщив ему свои "пожелания" с помощью функции XSetWMProperties()
.
Из листинга видно, что программа может сообщить менеджеру следующие параметры:
argc
и
argv
, передаваемые от UNIX программе;Имя окна и имя пиктограммы должны быть в начале преобразованы в "текстовые свойства", описываемые структурами типа XTextProperty
. Это выполняется функцией XStringListToTextProperty()
.
Для передачи информации о желаемой геометрии окна используется структура XSizeHints
.
X Window позволяет сообщить менеджеру также следующее:
После того, как "рекомендации" менеджеру окон переданы,
программа выбирает события, на которые она будет реагировать. Для этого
вызывается функция
XSelectInput()
. Ее последний аргумент есть комбинация битовых
масок (флагов). В нашем случае это
ExposureMask or KeyPressMask
.
ExposureMask
сообщает X Window, что программа обрабатывает
событие Expose
. Оно посылается
сервером каждый раз, когда окно должно быть перерисовано.
KeyPressMask
выбирает событие
KeyPress
- нажатие клавиши клавиатуры.
Теперь окно программы создано, но не показано на экране. Чтобы это произошло, надо вызвать функцию XMapWindow()
. Заметим, что из-за буферизации событий библиотекой Xlib, окно не будет реально нарисовано, пока программа не обратится к функции получения сообщений от сервера XNextEvent()
.
Программы для X построены по принципу управляемости событиями. Поэтому, после того, как окно создано, заданы необходимые параметры для менеджера окон, основная ее работа - это получать сообщения от сервера и откликаться на них. Выполняется это в бесконечном цикле. Очередное событие "вынимается" функцией XNextEvent()
. Само оно есть переменная типа XEvent
, который представляет собой объединение структур. Каждое событие Expose
, KeyPress
и т.д.) имеет свои данные (и, следовательно, свое поле в объединении XEvent
).
При получении сообщения Expose
программа перерисовывает окно. Это событие является одним из наиболее важных событий, которые приложение может получить. Оно будет послано нашему окну в одном из различных случаев:
Когда мы получаем событие
Expose
, мы должны взять данные события из члена
xexpose
объединения
XEvent
. Он содержит различные интересные
поля:
count
- количество других событий Expose
, ожидающие в очереди событий сервера. Это может быть полезным, если мы получаем несколько таких сообщений подряд - рекомендуется избегать перерисовывать окно, пока мы не получим последнее из их (то есть пока count
не равно 0).
window
- идентификатор окна, которому было послано сообщение
Expose
(в случае, если приложение зарегистрировало это событие
в различных окнах).
x
, y
- координаты верхнего левого угла области окна,
которая должна быть перерисована.
width
, height
- ширина и высота области окна,
которая должна быть перерисована.
Действия по обработке Expose
начинаются с создания графического контекста - структуры, которая содержит данные, необходимые для вывода информации, в нашем случае - текста:
gc = XCreateGC (display, window, 0, NULL);
После этого рисуется строка "First example!". Более графический контекст не нужен - он уничтожается:
XFreeGC (display, gc);
Окно может получить несколько событий Expose
одновременно. Чтобы не перерисовывать себя многократно, программа дожидается прихода последнего из них и только потом осуществляет вывод.
Приход события KeyPress
означает, что программу надо завершить:
прекратить связь с сервером: XCloseDisplay (display);
и вызвать функцию exit()
.
XCloseDisplay()
закрывает соединение с Х сервером, закрывает все окна и удаляет идентификаторы ресурсов, созданных клиентом на дисплее. Для удаления только окна без разрыва связи с Х сервером необходимо использовать функции XDestroyWindow()
и XDestroySubWindows()
.