2005.02_Piszemy program do zmiany rozdzielczości ekranu przy pomocy rozszerzenia RandR_[Programowanie].pdf

(585 KB) Pobierz
439111839 UNPDF
dla programistów
Piszemy program
do zmiany rozdzielczości
ekranu przy pomocy
rozszerzenia RandR
Marek Sawerwain
wytykaną przede wszystkich przez
początkujących użytkowników, jest
jego dość trudna konfiguracja.
Wszelkie parametry serwera X-ów zostają
zapisane w pliku tekstowym, więc najle-
piej przeprowadzać konfigurację poprzez
edycję tego pliku.
Inną poważną wadą był brak moż-
liwości zmiany parametrów wyświe-
tlanego obrazu bez restartu środowi-
ska. Co prawda, możliwa była zmiana
rozdzielczości ekranu poprzez kom-
binację klawiszy [ Ctrl ] + [ Alt ] + [ + ] oraz
[ Ctrl ] + [ Alt ] + [-], ale w ten sposób zmie-
nialiśmy tylko samą rozdzielczość,
a obraz wyświetlany przez X-y pozo-
stawał nadal tych samych rozmia-
rów. W rezultacie, przy zmianie roz-
dzielczości z wyższej na niższą, był
włączany tryb ekranu wirtualnego.
Ponadto, nie można było łatwo stero-
wać odświeżaniem obrazu.
Te problemy odeszły już do prze-
szłości wraz z wprowadzeniem roz-
szerzenia o nazwie RandR . Pozwala
ono również na wykorzystanie innych
możliwości kart graficznych, ponie-
waż, oprócz zmiany rozdzielczości
ekranu bez konieczności restartu śro-
dowiska X Window, możliwe stało się
także jego obracanie, jeśli tylko karta
graficzna udostępnia taką funkcjonal-
ność.
Okazuje się, że wykorzystanie tych
możliwości jest dość łatwe do zapro-
gramowania, więc w tym artykule
pokażę, jak napisać tego typu pro-
gram. W środowiskach typu GNOME
czy KDE, a nawet w samym środowi-
sku X Window, istnieją odpowiednie
programy wykorzystujące rozszerze-
nie RandR, ale napisanie takiego pro-
gramu samodzielnie to bardzo dobre
ćwiczenie.
Co ciekawe, nasz program będzie
oferował dostęp do wszystkich mo-
żliwych rozdzielczości. Przykładowo,
domyślnie dostępna aplikacja w środo-
wisku GNOME oferuje wyłącznie te
główne, takie jak 640x480, 800x600,
1024x768 itd. Z tego powodu mo-
żna powiedzieć (z lekkim przymruże-
niem oka), iż poprawimy niedostatki
środowiska GNOME.
Na płycie CD/DVD
Na płycie CD/DVD znajdują się
wykorzystywane biblioteki, kod
źródłowy programu oraz wszyst-
kie listingi z artykułu.
Projektujemy program
Program napiszemy wykorzystując biblio-
tekę GTK+. Do zaprojektowania gra-
ficznego interfejsu użytkownika wyko-
rzystamy program Glade . Tym razem
nie poprzestaniemy na wygenerowa-
niu pliku z opisem interfejsu (plik o roz-
szerzeniu glade ), ale przygotujemy rów-
nież kod źródłowy tworzący ten inter-
fejs – program, który opracujemy, jest
niewielkich rozmiarów, więc będzie naj-
lepiej, gdy nasza aplikacja nie będzie
wymagać do poprawnej pracy dodatko-
wych plików.
Możliwości naszego programu
można streścić jednym krótkim zda-
niem: program ma zmieniać roz-
dzielczość oraz wartość odświeża-
O autorze
Autor zajmuje się tworzeniem
oprogramowania dla WIN32
i Linuksa. Zainteresowania:
teoria języków programowania
oraz dobra literatura.
Kontakt z autorem:
autorzy@linux.com.pl
70 luty 2005
J edną z wad środowiska X Window ,
439111839.023.png 439111839.024.png 439111839.025.png 439111839.026.png 439111839.001.png
randr i gnome 2.8
dla programistów
Rysunek 1. Schemat przepływu najważniejszych zdarzeń w naszym programie
iż komponenty GtkComboBox nie będą
tworzone poprzez konstruktor gtk_
combo_box_new , ale przez gtk_combo_
box_new_text . Użycie tego drugiego
konstruktora daje nam dostęp do listy
elementów, która zostanie utworzo-
na samoczynnie przez system GTK+.
W ten sposób zostaniemy zwolnieni
z obowiązku tworzenia obiektu listy,
chociaż nadal będziemy musieli
wypełnić listę. Będą tam umieszczone
dostępne rozdzielczości ekranu oraz
wartości odświeżania. Nie zapomnijmy
także nazwać w odpowiedni sposób
poszczególnych widgetów. W naszym
programie widget GtkComboBox , zawie-
rający dopuszczalne nazwy rozdziel-
czości, to ResolutionWidget . Drugi
widget tego typu, w którym wybiera-
my wartość odświeżania, nosi nazwę
RefreshWidget .
Jak już wcześniej podałem, nasz
program napiszemy na dwa sposoby.
W pierwszym zastosujemy dodatkowy
plik z opisem interfejsu, a w drugim,
za pomocą Glade i opcji Build z menu
File , wygenerujemy kod źródłowy,
w którym będzie tworzony interfejs.
Zastosowanie tego kodu pozwoli
nam na utworzenie programu, który nie
będzie wymagał dodatkowych plików.
Jak się okaże, między tymi dwoma tech-
nikami, które dostarcza nam program
Glade, w finalnym kodzie aplikacji róż-
nice będą tylko kosmetyczne.
nia ekranu. Pomimo tego, jak zawsze,
warto zaprojektować schemat dzia-
łania programu. Prezentuje go Rysu-
nek 1. Najważniejszym zdarzeniem
jest zmiana rozdzielczości ekranu, ale,
jak widać, sam proces wyboru pożą-
danej rozdzielczości pociąga za sobą
wybór zbioru dopuszczalnych wartości
odświeżania. Jest to jedyny trudniejszy
fragment do opracowania w naszym
programie.
warto wspomnieć. Gdy umieścimy
na formularzu obydwie kontrolki, to
zazwyczaj nie dodajemy żadnych ele-
mentów do wyboru, gdyż robimy to
później samodzielnie w programie.
Okazuje się, że to podejście tym razem
nie będzie działało, gdyż po załado-
waniu pliku glade z opisem interfej-
su, każda próba dodania elementów do
GtkComboBox będzie generować infor-
macje o błędach. Sposób uniknięcia
tego problemu jest bardzo prosty. Po
umieszczeniu komponentu GtkCombo-
Box w tworzonym oknie, do listy ele-
mentów wystarczy dodać kilka war-
tości, po czym zapisać plik, a następ-
nie usunąć dodane elementy i ponow-
nie zapisać projekt. Spowoduje to, że
w opisie XML zostanie odznaczone,
Odczyt podstawowych
informacji
Pomijając na razie sposób, w jaki napi-
szemy program, istotne są dla nas
informacje o dostępnych rozdzielczo-
ściach. Ich spis uzyskujemy tylko raz
w naszym programie, korzystając z API
systemu X Window. Z tego powodu na
początku należy dołączyć odpowied-
nie pliki nagłówkowe:
Budujemy interfejs
Przy tworzeniu oprogramowania w GTK+/
GNOME do przygotowania graficzne-
go interfejsu użytkownika najlepiej
wykorzystać program Glade . Rysu-
nek 2 pokazuje interfejs naszej apli-
kacji. Nie jest on zbyt skompli-
kowany, gdyż stosujemy tylko dwa
widgety GtkComboBox oraz dwa stan-
dardowe przyciski: jeden do zmiany
rozdzielczości ekranu oraz jeden
kończący działanie programu. Dodat-
kowo, umieścimy jeszcze kilka
etykiet. W jednej z nich znajdzie
się aktualnie wybrana rozdzielczość.
Sam proces tworzenia interfejsu jest
na tyle typowy, że nie będę poświęcał
mu zbyt dużo czasu, ale przy widgetach
GtkComboBox ujawnia się pewne niedo-
ciągnięcie programu Glade, o którym
Listing 1. Pętla wpisująca dostępne rozdzielczości do listy wyboru widgetu
ResolutionWidget
int i ;
gchar tmp_buf [ 128 ];
...
for ( i = 0 ; i < nsize ; i ++) {
g_snprintf ( tmp_buf , 128 , "%5d x %-5d" , sizes [ i ] . width , sizes [ i ] . height );
gtk_combo_box_append_text ( GTK_COMBO_BOX ( ResolutionWidget ) , & tmp_buf [ 0 ]);
}
www.lpmagazine.org
71
439111839.002.png 439111839.003.png 439111839.004.png 439111839.005.png 439111839.006.png 439111839.007.png 439111839.008.png 439111839.009.png 439111839.010.png
 
dla programistów
Listing 2. Obsługa sygnału changed dla widgetu wyboru rozdzielczości
Przedstawiony powyżej kod zapewnia
nam spis dostępnych rozdzielczości.
W naszym programie czynność tę
wykonuje funkcja get_basic_informa-
tion .
W tym momencie musimy prze-
nieść uzyskane informacje do widgetu
o nazwie ResolutionWidget . W tym celu
przepisujemy zawartość tablicy sizes .
Omawiany proces może wydawać się
skomplikowany, ale sprowadza się do
jednej krótkiej pętli typu for , której kod
znajduje się na Listingu 1.
Podczas przepisywania ważna
jest całkowita ilość elementów tabli-
cy sizes . Wielkość tę poznaliśmy pod-
czas wywołania funkcji XRRConfigSizes
i została zapisana w zmiennej nsize .
W samej pętli wykonujemy tylko dwie
czynności. Instrukcją g_snprintf two-
rzymy odpowiednio sformatowany ciąg
znaków, np. 1024 x 768 , a następnie
wywołaniem gtk_combo_box_append_
text dodajemy ten ciąg do listy wyboru
widgetu ResolutionWidget .
Jak widać, czynności, które trzeba
wykonać, aby uzyskać listę dostęp-
nych rozdzielczości ekranu, są try-
wialne. W kodzie naszej aplikacji tym
zadaniem zajmuje się funkcja o nazwie
fill_resolution_list . Wykonuje ona
jeszcze jedną ważną czynność, a mia-
nowicie wpisuje do etykiety aktualne
parametry obrazu. Kod, który wykonu-
je te czynności, jest następujący:
void on_ResolutionWidget_changed ( GtkWidget * widget , gpointer data )
{
int i = 0 ;
gchar tmp_buf [ 128 ];
i = gtk_combo_box_get_active ( GTK_COMBO_BOX ( widget ));
selected_resolution = i ;
for ( i = 0 ; i < old_nrate ; i ++)
gtk_combo_box_remove_text ( GTK_COMBO_BOX ( RefreshWidget ) , 0 );
rates = XRRConfigRates ( screen_config , selected_resolution , & nrate );
old_nrate = nrate ;
for ( i = 0 ; i < nrate ; i ++)
{
g_snprintf ( tmp_buf , 128 , "%3d" , rates [ i ] );
gtk_combo_box_append_text ( GTK_COMBO_BOX ( RefreshWidget ) , & tmp_buf [ 0 ]);
}
sel_res = TRUE ;
}
#include <X11/Xproto.h>
#include <X11/Xlib.h>
#include <X11/extensions/Xrandr.h>
#include <X11/extensions/Xrender.h>
W kolejnym kroku pobieramy indeks
aktualnie stosowanej rozdzielczości
ekranu:
Najważniejszy jest dla nas przedostat-
ni plik, bo w nim znajdują się funkcje
związane z rozszerzeniem RandR. Uzy-
skane informacje należy gdzieś umie-
ścić, więc zdefiniujemy kilka zmien-
nych. Pierwsza z nich reprezentuje
aktualną konfigurację:
current_size = XRRConfigCurrent
Configuration (screen_config,
&current_rotation);
Możliwości RandR nie ograniczają się
wyłącznie do zmiany rozdzielczości
ekranu. Obraz możemy obracać o 90
stopni, więc w powyższej linii kodu
należy podać adres do zmiennej,
w której zostanie umieszczona infor-
macja o obrocie ekranu.
Samą tablicę zawierającą dostę-
pne rozdzielczości ekranu odczytu-
jemy za pomocą funkcji XRRConfig-
Sizes :
§
"%5d x %-5d@%3d Hz", sizes
§
§
sizes[current_size].height,
§
XRRConfigCurrentRate(screen_config));
XRRScreenConfiguration *screen_config;
§
§
(GTK_LABEL(ActualResLabel), tmp_buf);
Następna to w rzeczywistości tabli-
ca, w której zostaną umieszczone
informacje o dostępnych rozdzielczo-
ściach:
gtk_label_set_text
Wykorzystaliśmy zmienną current
_size . Jej wartością jest indeks, pod
którym w tablicy sizes znajduje się
opis aktualnie stosowanej rozdzielczo-
ści. Wartość odświeżania odczytujemy
za pomocą funkcji XRRConfigCurrent-
Rate , która w wyniku daje dokładną
wartość odświeżania.
XRRScreenSize *sizes;
§
(screen_config, &nsize);
Proces odczytu spisu rozdzielczości roz-
poczynamy od uzyskania uchwytu:
Ważną rolę pełni zmienna nsize . Jest
w niej umieszczana liczba dostęp-
nych rozdzielczości ekranu. Chociaż
nasz program służy tylko do zmiany
rozdzielczości, to możemy również
odczytać dostępne sposoby obraca-
nia ekranu:
screen_config = XRRGetScreenInfo
(GDK_DISPLAY(), GDK_ROOT_WINDOW());
Wybieramy rozdzielczość
oraz odświeżanie
Podczas projektowania interfejsu w wid-
getach GtkComboBox trzeba koniecz-
nie zaimplementować obsługę syg-
nału changed , gdyż po wyborze roz-
Jak widać, wykorzystujemy predefi-
niowane funkcje z podsystemu GDK,
reprezentujące okno główne: GDK_ROOT_
WINDOW , oraz urządzenie GDK_DISPLAY ,
z którego korzysta system GTK+.
§
(screen_config, &current_rotation);
72
luty 2005
g_snprintf(tmp_buf, 128,
[current_size].width,
sizes = XRRConfigSizes
rotations = XRRConfigRotations
439111839.011.png 439111839.012.png
 
randr i gnome 2.8
dla programistów
nej old_nrate . Później musimy przenieść
w pętli zawartość tablicy rates do
komponentu RefreshWidget , w podob-
ny sposób, jak w przypadku tablicy
sizes . Na koniec wykonujemy jeszcze
jedną czynność, a mianowicie ustawia-
my flagę sel_res na TRUE . W ten sposób
oznaczamy, że użytkownik dokonał
wyboru rozdzielczości.
Zmieniamy rozdzielczość
Listing 3 zawiera kod odpowiedzialny
za zmianę rozdzielczości i odświeżania
ekranu. Znajdująca się tam funkcja jest
wywoływana w momencie naciśnię-
cia przycisku Change resolution . Naj-
ważniejszym elementem jest wywoła-
niem funkcji:
XRRSetScreenConfigAndRate
Rysunek 2. Program do zmiany rozdzielczości ekranu
Ustala ono rozdzielczość ekranu oraz
wartość odświeżania:
dzielczości ekranu bądź wartości
odświeżania z rozwijanej listy, ten
wybór musi zostać w jakiś sposób
zapamiętany.
W przypadku komponentu Refresh-
Widget sprawa jest bardzo prosta, gdyż
wystarczy tylko zapamiętać indeks ele-
mentu, co pozwala na odczyt wartości
z odpowiedniej tablicy, podobnie jak
w przypadku dostępnych rozdzielczości.
Dodatkowo, ustawiamy specjalną flagę,
informującą o tym, że wybór odświe-
żania został już dokonany. Kod funk-
cji, która reaguje na sygnał o wskazaniu
wartości dla widgetu wyboru wartości
odświeżania, jest bardzo krótki:
dzielczości, jak wiadomo, jest tabli-
cą, więc wystarczy poznać indeks roz-
dzielczości wskazanej przez użytkow-
nika. Możemy tak zrobić, gdyż przeno-
sząc dane z tablicy sizes do listy elemen-
tów widgetu ResolutionWidget zacho-
waliśmy tę samą kolejność oraz warto-
ści indeksów.
Zadaniem następnej pętli jest usu-
nięcie poprzednio wpisanych warto-
ści odświeżania. W tym celu stosujemy
gtk_combo_box_remove_text , wywoła-
ne dokładnie kilkanaście razy, w zależ-
ności do tego, ile było dopuszczalnych
wartości odświeżania. Ilość jest prze-
chowywana w zmiennej old_nrate .
Warto zapytać się, co stanie się, gdy
lista dostępnych wartości odświeżania
będzie pusta. Funkcja usuwająca ele-
ment z listy widgetu powinna zgłosić
błąd. Tak się nie stanie, gdyż zmienna
old_nrate na początku pracy aplikacji
zawiera zero i pętla for nie wykona ani
jednej iteracji.
Ostatni fragment wypełnia listę
komponentu RefreshWidget . Pierw-
szym krokiem jest wywołanie funk-
cji XRRConfigRates . W jej wyniku
otrzymujemy tablicę z wartościami
odświeżania dla wybranej rozdziel-
czości, zapisanej w zmiennej selec-
ted_resolution . Ważna jest również
ilość dostępnych wartości odświe-
żania i tę wielkość zapamiętujemy
w zmiennej nrate . Następnie przenosimy
ilość dostępnych odświeżań do zmien-
§
screen_config, GDK_ROOT_WINDOW(),
§
§
selected_resolution, current_rotation,
§
selected_rate, CurrentTime);
Trzy pierwsze argumenty wskazują na
urządzenie oraz uchwyt reprezentu-
jący aktualną konfigurację. Rozdziel-
czość ustalamy czwartym argumen-
tem, przy czym, jak widać, jest to tylko
indeks wskazujący na element tablicy
sizes . Następna zmienna wskazuje na
obrót ekranu. W naszym programie
nie zajmujemy się tym problemem,
więc tylko kopiujemy tę wartość.
W następnym parametrze określamy
wartość odświeżania. Nie jest to tym
razem indeks odnoszący się do tabli-
cy, ale rzeczywista wartość odświeża-
nia. W ostatnim parametrze podajemy
aktualny czas.
Wywołanie funkcji XRRSetScreen-
ConfigAndRate to najważniejsza czyn-
ność, którą musimy wykonać. W kodzie
z Listingu 3, przed zmianą rozdzielczo-
ści, upewniamy się jednak, sprawdza-
jąc wartość flag sel_res oraz sel_ref ,
czy użytkownik istotnie wybrał roz-
dzielczość i odświeżanie.
Po ustaleniu rozdzielczości wywo-
łujemy XRRConfigCurrentConfigura-
tion , aby pobrać parametry obrazu
i aktualizujemy etykietę, w której te
informacje są prezentowane. Następ-
§
( GtkWidget *widget, gpointer data ) {
selected_refresh=
§
(GTK_COMBO_BOX(widget));
selected_rate=rates[selected_refresh];
sel_ref=TRUE;
}
W przypadku rozdzielczości ekranu,
kod jest już znacznie bardziej skom-
plikowany. Listing 2 zawiera pełną
treść funkcji, która jest wywoływana
w momencie pojawienia się zdarzenia
changed .
Pierwsze dwie linie po deklaracji
zmiennych są konieczne, abyśmy pozna-
li, jaką rozdzielczość wskazał użytkow-
nik. Zmienna sizes , która zawiera roz-
www.lpmagazine.org
73
status=XRRSetScreenConfigAndRate
(GDK_DISPLAY(),
void on_RefreshWidget_changed
§
gtk_combo_box_get_active
439111839.013.png 439111839.014.png 439111839.015.png 439111839.016.png 439111839.017.png
 
dla programistów
Wykluczenie pliku glade będzie
wymagało samodzielnego budowania
interfejsu. I w tym przypadku pomoc-
ny okazuje się program Glade. Jak już
wcześniej napisałem, po wybraniu opcji
Build z menu File , samodzielnie utwo-
rzy on szkielet aplikacji na podstawie
utworzonego przez nas okna. Powsta-
nie cała struktura katalogowa, w której
najbardziej interesują nas pliki z kata-
logu src .
Do naszego programu potrzebuje-
my pięć plików. Dwa pierwsze to inter-
face.c oraz interface.h . Zawierają one
implementację funkcji:
create_MainWin
Rysunek 3. Projektowanie interfejsu programu w Glade
która tworzy zaprojektowane przez nas
okno. Potrzebne będą również pliki
support.c oraz support.h , w których
znajdziemy kilka pomocniczych funk-
cji i makr. Wykorzystamy także plik
o nazwie callbacks.h , w którym znajdują
się nagłówki funkcji reagujących na zda-
rzeniach. Implementację tych funkcji już
zawiera nasz program. Plik callbacks.h
dołączamy do naszego programu, gdyż
plik interface.c wymaga tego pliku,
a przecież nie będziemy modyfikować
bez wyraźnego powodu plików genero-
wanych automatycznie.
Przy tej okazji trzeba ponownie
wspomnieć o konstruktorach kom-
ponentów GtkComboBox . Glade może
wygenerować plik interface.c , w któ-
rym będzie stosowany konstruktor
gtk_combo_box_new , zamiast potrzeb-
nego nam gtk_combo_box_new_text .
Należy więc sprawdzić, jak wygląda
plik interface.c pod kątem tych kon-
kretnych funkcji.
Kod, który trzeba dodać do nasze-
go programu, nie jest zbyt wielki. Na
początek możemy usunąć kilka lini-
jek związanych z obsługą libglade . Nie-
potrzebne są pliki nagłówkowe oraz
deklaracja obiektu xml . Po wywołaniu
gtk_init tworzymy okno naszej aplika-
cji w następujący sposób:
nie wpisujemy do naszych flag war-
tości FALSE.
MainWin=glade_xml_get_widget(xml,
§
"MainWin");
Wersja programu
z plikiem glade
Opisane dotychczas funkcje są wystar-
czające, aby zmieniać rozdzielczość oraz
odświeżanie ekranu, ale musimy zdecy-
dować, w jaki sposób będziemy budo-
wać interfejs.
Najłatwiejszy sposób to stworzenie
interfejsu za pomocą programu Glade
i zapisanie go do pliku o rozszerze-
niu glade . Taki plik wczytujemy do
naszego programu wykorzystując
bibliotekę libglade . Zanim cokolwiek
zrobimy, należy zainicjować GTK+.
Ta operacja wymaga wyłącznie jednej
linii:
Podłączenie sygnałów następuje w spo-
sób automatyczny – wystarczy wywo-
łać funkcję:
glade_xml_signal_autoconnect
Tego typu kod, jak zwykle, umiesz-
czamy w funkcji main . Przed ostatecz-
nym uruchomieniem programu wyko-
nujemy jeszcze jedną ważną czynność,
a mianowicie wypełniamy listę widge-
tu ResolutionWidget w sposób, który
opisałem we wcześniejszej części tego
artykułu. Po tej czynności możemy już
uruchomić program wywołując funkcję
gtk_main .
gtk_init(&argc, &argv);
Wersja samodzielna
Wersja z plikiem glade ma tę zaletę,
że istotnie łatwo jest napisać kod,
który wczyta plik z opisem inter-
fejsu. Ponadto, jednym poleceniem
łączymy zdarzenia z funkcjami.
Wadą tego rozwiązania jest to, że
plik glade musi być dystrybuowany
razem z aplikacją. O ile w przy-
padku dużego programu nie stanowi
to większego problemu, to w na-
szej aplikacji nie to najlepsze roz-
wiązanie. Najlepiej, aby nasz program
nie wymagał żadnych dodatkowych
plików, poza środowiskiem X Window
oraz GNOME.
Wczytanie pliku glade to również tylko
jedna linia kodu:
xml = glade_xml_new("main_app_win.glade",
§
NULL, NULL);
Następną czynnością, którą dość często
trzeba wykonać w programach GTK/
GNOME, korzystających z plików glade ,
jest uzyskanie referencji do potrzeb-
nych widgetów. Dokonujemy tego
funkcją glade_xml_get_widget . Przykła-
dowo, dla okna głównego wygląda to
następująco:
MainWin=create_MainWin();
Następnie, podobnie jak to było
w poprzedniej wersji programu, uzy-
skujemy odniesienia do widgetów.
Korzystamy z gotowej funkcji lookup_
widget , która znajduje się w pliku sup-
port.c . Referencje do trzech pozostałych
74
luty 2005
439111839.018.png 439111839.019.png 439111839.020.png 439111839.021.png 439111839.022.png
 
Zgłoś jeśli naruszono regulamin