Szilard@BalaBit

Helló Window: Szignálkezelés dióhéjban

péntek, december 21, 2012 @ 09:12 DE. Author: Pfeiffer Szilárd

Fogalmak

Először is a korábban már megismert alapfogalmakat vesszük újra elő, most azonban általános ismertetésük helyett a témánkhoz konkrétan kapcsolódó specifikumaikat vázoljuk fel.

Main Loop

Ahogy arról az előző részekben már szó esett – más felületprogramozási nyelvekhez teljesen hasonlóan – a GTK is eseményvezérelt (event-driven) módon működik. Ez annyit tesz, hogy felhasználói interakciók bekövetkeztéig – figyelmen kívül hagyva az ütemezett eseményeket és néhány, a későbbi részekben részletezendő funkciót –, a GTK a saját főciklusában (main loop) várakozik, lényegében tehát a szoftver futását maguk a bekövetkező események vezérlik, hiszen ezek híján a várakozás sosem ér véget. Felhasználói interakció lehet például az egér megmozdítása, vagy egy kattintás, esetleg egy billentyű lenyomása, vagy éppen felengedése.

Ahhoz, hog az alkalmazás az eseményvezérelt szakasza megkezdődhessen be kell lépni a GTK főciklusába. Erre, az előző részben taglalt minimalista alkalmazások forráskódjából már ismerős gtk_main(), gtkmm esetén a Gtk::Main::run(), illetve a Python változat esetén a Gtk.main() függvény szolgál. Ha az imént említett események közül bármelyik bekövetkezik, az addig “alvó” főciklus úgymond “felébred”, azonosítja az eseményhez tartozó felületi elemet, majd a bekövetkezett eseményt továbbítja (propagate) az azonosított widget, vagy widgetek felé. A vezérlés ezt követően a főciklusba tér vissza, ahol folytatódik a várakozás a következő eseményre.

Signal

A szignál voltaképpen egy névvel azonosított, a bekövetkezett esemény továbbítására szolgáló üzenet, amit az osztály definiál, hogy példányai értesíthessék az események bekövetkeztéről az az iránt érdeklődőket. Ilyen üzenetből számos létezik, hisz az egyes widgettípusokon különböző események lehetnek értelmezettek. Gomb esetén a rá történő kattintás, egy legördülő menünél az egér menüelem fölé történő mozgatása, míg egy widgetnél például annak átméretezése. Minden ilyen esemény rendelkezik saját névvel, melynek révén hivatkozni tudunk rá (pl: button-pressed, enter-notify-event, size-request). A szignálok öröklődnek, azaz egy specifikus widget, mint amilyen mondjuk egy RadioButton, vagy egy CheckButton, minden olyan szignállal rendelkezik, amivel őse a Button, vagy akár annak az őse az a Widget típus rendelkezett.

A szignálok egyrészről arra szolgálnak, hogy a GTK rendszerén belül az egyes widgetek egymással kommunikálhassanak. Ha például egy gombot lenyomunk, akkor azt (illetve annak részeit) újra kell rajzolni, ha egy menüelemet kiválasztunk, azt át kell színezni, illetve az esetleges almenüpontokat ki kell rajzolni, míg átméretezésnél az egyes widgetek helyigényét újra ki kell számolni. Másfelől ha a program írói valamely esemény bekövetkezéséről értesülni szeretnének, megadhatnánk eseménykezelő függvényeket, melyek ezen esetekben meghívódnak.

Callback

Ezen eseménykezelő függvények elnevezése a GTK terminológiában callback. Az egyes eseményekhez tartozó kezelőfüggvények prototípusai a szignál fajtájától függenek. A C nyelvű változat esetén első paraméterük jellemzően az a Widget – pontosabban szólva Object, hiszen a szignálkezelés ezen a szinten került implementálásra a Glib-ben – melyen az esemény kiváltódott. Ezt a paramétert követik a szignálhoz kapcsolódó egyéb jellemzők, az utolsó pedig a szignál bekötésekor megadott, úgynevezett user data, amiről a példaprogram kapcsán részletesebben szólunk. Elöljáróban csak annyit, hogy ez egy meglehetősen kényelmetlen és gyakorta nehézkesen használható megoldás, melyre a C++, illetve Python nyelvű változatok kínálnak kényelmes alternatívát.

Szignálkezelés

Az előző szám módszertanától eltérve az alábbiak szerint elemezzük a kódokat:

  • külön-külön vesszük számba ez egyes nyelvi változatok sajátosságait
  • először a C, illetve a C++ nyelvű verziónak fogunk neki, ezt követően bemutatjuk mennyiben más a helyzet, Python nyelvű változatokban
  • a kódot nem sorfolytonosan, hanem a futás logikája szerint követjük, lévén egy kicsit is bonyolultabb esetben – mint amilyennek az alábbi példa is mondható – már ez a logikusabb

C, illetve C++ nyelvű változat

 

  1. #include <gtk/gtk.h>
  2.  
  3.  
  4. static void on_button_clicked( GtkWidget *widget, gpointer data)
  5. {
  6. g_print ("%s\n", (const char *) data);
  7. }
  8.  
  9.  
  10.  
  11.  
  12. static gboolean on_delete_event(GtkWidget *widget,
  13. GdkEvent *event,
  14. gpointer data )
  15. {
  16. g_print ("delete event occurred\n");
  17. return TRUE;
  18. }
  19.  
  20.  
  21. GtkWidget * my_window_new()
  22.  
  23. {
  24. GtkWidget *window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
  25. GtkWidget *button = gtk_button_new_with_label ("Hello Window!");
  26.  
  27. g_signal_connect (G_OBJECT (window), "delete-event",
  28. G_CALLBACK (on_delete_event), NULL);
  29. g_signal_connect (G_OBJECT (window), "destroy",
  30. G_CALLBACK (gtk_main_quit), NULL);
  31.  
  32. g_signal_connect (G_OBJECT (button), "clicked",
  33. G_CALLBACK (on_button_clicked),
  34. "Hello Window!");
  35. g_signal_connect_swapped (G_OBJECT (button), "clicked",
  36. G_CALLBACK (gtk_widget_destroy),
  37. G_OBJECT (window));
  38.  
  39. gtk_container_add (GTK_CONTAINER (window), button);
  40.  
  41. gtk_widget_show (button);
  42.  
  43. return window;
  44. }
  45.  
  46.  
  47.  
  48.  
  49.  
  50. int main(int argc, char *argv[])
  51. {
  52. GtkWidget *window;
  53.  
  54. gtk_init (&argc, &argv);
  55.  
  56. window = my_window_new();
  57. gtk_widget_show (window);
  58.  
  59. gtk_main ();
  60.  
  61. return 0;
  62. }

Általánosságok

12 sor
A fejlécállományok beszerkesztésének sajátosságairól már esett szó, így az egyedüli specifikum itt a C++ változat által beszerkesztett iostream fejlécfájl, amire csupán a képernyőre történő íráshoz lesz szükség. A GTK+ esetén – mivel a gtk.h minden szükséges fejléc állományt maga felsorol – használni tudjuk a Glib erre a célra használatos függvénylét (g_print).
5062 sor
A main függvényben alkalmazottak gyakorlatilag teljesen azonosak a korábbi minimális példánál ismertetettekkel, így itt erről leginkább csak annyit érdemes megjegyezni, hogy a C++ változat – élve a nyelv adta lehetőségekkel – egy saját osztály segítségével zárja egységbe egy gombbal kiegészített ablakunkat, míg a C változatban egy – a saját ablak létrehozására szolgáló – függvény segítségével igyekeztünk ezt módszert valamelyest követni. Természetesen a GTK+ esetén is van mód származtatásra, de nem oly kézenfekvő módon, mint amikor a C++ nyelvet használjuk, ennél fogva ennek ismertetése még várat magára.

Szignálok a GTK+ nyelvű kódban

2425 sor
A létrehozott ablak, illetve gomb tárolására szolgáló változók típusa GtkWidget, a specifikusabb GtkWindow, illetve GtkButton helyett. Ennek magyarázata, hogy minden olyan függvény a GTK+-ban, melynek segítségével egy új widgetet hozhatunk létre – gyakorlatilag a _new végű metódusok – egy GtkWidget típusú objektumra mutatóval tér vissza. Ennek több oka is van. Egyrészről kényelmi, hogy elkerülhessük a folytonos típuskényszerítéseket, hisz számos esetben olyan függvényeket használunk, melyek amúgy is GtkWidgeteket kezelnek, tehát ilyen típusra mutatót vesznek át első paraméterként. Másrészről ha egy specifikus – mondjuk GtkButton-t kezelő – függvényt akarunk hívni, akkor vagy fordítási – vagy ami inkább javasolt – futás idejű típuskényszerítés alkalmazandó (pl: GTK_BUTTON, GTK_WINDOW), aminek megvan az a komoly előnye, hogy ha sikerült a mutatónkat, vagy a mutatott objektumot valamilyen módon korrumpálni, akkor arra viszonylag hamar fény tud derülni.

Szignálkezelő függvények felkötése

27 sor
Az első szignálbekötés. Viszonylagos egyszerűsége ellenére számos apróságra érdemes figyelmet fordítani. Az első maga a g_signal_connect kulcsszó, ami függvénynek tűnhet, pedig ugyanúgy, mint a g_signal_connect_swapped, makró, amik a g_sinal_connect_data függvényt burkolják. A soron következő érdekesség a G_OBJECT makró, ami futás idejű típusellenőrzést hajt végre a neki megadott paraméteren, majd egy GObject típusra mutatóval tér vissza. A megrögzött C++ programozók joggal kérdezhetik, mi szükség erre, hisz egyfelől majd elvégzi a típusellenőrzést a fordító, meg hát a GtkWindow típus úgy is leszármazottja a GObject ”osztálynak“. Ez így is lenne, na de ez itt C, tehát ős-, illetve származtatott osztályokról csak logikai értelemben lehet szó, a típusellenőrzés tehát nem végezhető, sőt minden esetben a hívott függvénynek megfelelő típuskényszerítő makrót célszerű alkalmazni.A második paraméter a szignál neve, amivel azt adjuk meg, hogy az előző paraméterként megadott object melyik szignáljára is szeretnénk kezelő függvényt (callback) kötni. A harmadik paraméter azon függvény címe, aminek meghívódását ki szeretnénk váltani az esemény bekövetkezésekor. A függvénynevet itt is egy makró segítségével adjuk át, ami az előzőekhez hasonlóan C nyelvi hiányosságokra vezethető vissza. Mivel a meghívandó callbackek prototípusai igen sokfélék lehetnek (ami magából a példából is látszik valamelyest és ezek mind külön típusnak minősülnek a C nyelvben ezért ahányféle callback variáció létezik, annyiféle g_signal_connect függvényre lenne szükség. Könnyen belátható, hogy a jogos lustaság más irányba vitte a GTK+ fejlesztőit. A G_CALLBACK tulajdonképpen egy fordítási idejű típuskényszerítés egy általános függvénytípusra, amivel ugyan megoldottuk, hogy csak egyetlen g_signal_connect_data függvényre legyen szükség, de elvesztettünk minden nemű típusbiztosságot. Ha például egy az adott szignálnak nem megfelelő típusú függvényt adunk meg paraméterként, amit a példabeli függvénynevek felcserélésével könnyen megtehetünk, csúnya meglepetésekben lesz részünk, de csak futásidőben. Nem hagyhatjuk továbbá figyelmen kívül a C nyelv azon sajátosságát sem, hogy az átadott függvényparaméterek átvétele nemkötelező, azaz ha kevesebb paraméterrel definiálunk egy függvény, mit amennyivel hívni szeretnénk voltaképpen nem követünk el bűnt, ez viszont tovább bonyolítja a helyzetet.
Az utolsó paraméter az úgynevezett user data, ami arra szolgál, hogy az eseménykezelő függvényünknek olyan adatokat adjunk át, amik az esemény megtörténtéből nem következnek. Ilyenek lehetnek például más widgetek címei, ahogy azt látni is fogjuk. Ez esetben az átadott paraméter NULL, ami szintén egy makró ami egy jól nevelt ((void*) 0) kifejezésre fejtődik ki C kód esetén. Zárszóként ehhez a sorhoz csak annyit, hogy a delete_event eseményt az ablakkezelő váltja ki, akkor, amikor az ablakot valamilyen módon (billentyűzet, menü, egér) bezárjuk.

A delete-event szignál blokkolása

12 sor
Ez a delete_event szignálkezelő függvénye, aminek – néhány más szignálkezelő függvényhez hasonlóan – egy gboolean értékkel kell visszatérnie, ami azt határozza meg, hogy az általunk a szignálkezelőben végrehajtottak után a GTK lefuttassa-e saját szignálkezelő rutinját, vagy sem. Jelentése voltaképpen tehát az, hogy a saját magunk a szignált mindenre kiterjedően kezeltük-e. Ennek megfelelően ha a visszatérési érték 0 – azaz logikai hamis –, akkor végrehajtódik a GTK+ adott szignálhoz kapcsolódó alapértelmezett kezelő függvénye, ellenkező esetben értelemszerűen arra utasítjuk a GTK-t, hogy a szignál további feldolgozásától tekintsen el. Itt érdemes felhívni a figyelmet arra, hogy mivel a C nyelvben – a C++-al ellentétben –, nincs bool típus annak analógiájára definiálták a gboolean típust (ami tulajdonképpen egy int) és a két megfelelő logikai értéket makróként (TRUE, FALSE).
Ebben a konkrét esetben (delete_event szignál) az alapértelmezett szignálkezelő a gtk_widget_destroy függvény, vagyis ha nem kötünk fel saját szignálkezelő függvényt, vagy logikai hamis értékkel térünk vissza a kezelő függvényből, akkor a window objektum megsemmisül, az ablak bezárul. Logikai igaz érték visszaadásával elérhető, hogy hiába próbáljuk akár a jobb felső sarok x gombjának megnyomásával, akár valamilyen billentyűkombináció révén bezárni az ablakot ez a próbálkozás sikertelen lesz, ellenben minden ilyen próbálkozás egy újabb sor kiírást eredményezi.

Az ablak bezárása

29 sor
Az előzőhöz teljesen hasonló módon itt a destroy szignálra kötünk be eseménykezelőt, ami a widget életciklusának végén váltódik ki. Egy konténerben lévő widget esetén ez a konténerből való eltávolítás következménye – már ha valaki nem tart külön referenciát az eltávolított widgetre –, legfelső szintű widgetek (toplvel) esetén – mint amilyenek az ablakok is – ez jellemzően a gtk_widget_destroy függvény meghívásának folyománya, lévén az ilyen widgetek automatikusan nem semmisülnek meg, erről nekünk explicit módon kell gondoskodnunk (35. sor).A destroy szignál kezelése általánosságban nézve ritka, jelen esetben is csak az a szerepe, hogy a program valamilyen módon ki tudjon lépni. Az ablak bezárása alapértelmezetten ugyan ezt eredményezné, de a delete-event szignálra kötött kezelőfüggvényben nem hogy ezt nem tesszük meg, de még az ablak bezáródásást is meggátoljuk. Mikor a destroy szignálra kötött kezelőfüggvény meghívódik, ablakunk épp megszűnőfélben van, ugyanakkor ha ez az eset áll is fenn a programunk futása annak ellenére sem érne véget, hogy az ablakunk bezáródik, hiszen a main loopból nem lépnénk ki. Ezen helyzet elkerülésére eseménykezelőként a main loopból való kilépésre szolgáló gtk_main_quit függvényt kötjük fel.
Érdemes megjegyezni, hogy bár a gtk_main_quit függvény definíciója (void (*) ()) nem felel meg tökéletesen a destroy szignál által elvártaknak (void (*) (GtkWidget *, gpointer)) ez voltaképpen nem jelent problémát, hiszen a típusok különbözőségét a G_CALLBACK makró által alkalmazott típuskényszerítés elrejti a fordító elől, futásidőben pedig a gtk_main_quit egész egyszerűen nem veszi át a szignálkezelőt meghívó kódtól a két függvényparamétert.
35. sor
A 32. sortól csak a meghívandó eseménykezelő függvényben, illetve az annak átadandó paraméterekben sorrendjében tér el. Ahogy az a függvény nevéből (g_signal_connect_swapped) következik, arról van szó, hogy a gomb lenyomásakor meghívandó callback – jelen esetben a gtk_widget_destroy – paramétereiben a user_data, illetve az az object, amin az esemény kiváltódik, felcserélésre kerül. Kicsit konkrétabban fogalmazva a user_data lesz a callback első paramétere és a gomb a második. Mivel itt a callback a gtk_widget_destroy függvény, ami paraméterként mondjuk úgy, a törlendő widgetet várja, a user_data pedig az ablakunk, nem nehéz kitalálni, hogy a gombra való kattintás eredményeként az ablak meg fog szűnni, de csak azután, hogy a “Helló Window!” üzenet megjelent a konzolban.

Gomb lenyomásának kezelése

32. sor
Eseménykezelő függvény bekötése a gomb clicked szignáljára, ami a gomb lenyomásakor hívódik meg, aminek egyetlen különlegessége, hogy itt a szignálkezelő definíciója pontosan megfelel a szignál által elvártaknak, ugyanakkor a G_CALLBACK makró mégis szükséges, mivel a g_signal_connect azt a típust várja, amire az on_button_clickedfüggvényt a makró kényszeríti.
4. sor
A fenti állítás – miszerint az ablak csak a kiírást követően szűnik meg – csak azért igaz, mert a on_button_clicked függvény, mint eseménykezelő előbb kerül felkötésre, mint a gtk_widget_destroy, valamint azért, mert az eseménykezelők alapvetően a felkötés sorrendjében kerülnek meghívásra. Fordított esetben előbb hívódna meg a destroy az ablakra, ami – sok egyéb mellett – leköti az eseménykezelő függvényeket, így a kiírást nem is látnánk.

Egyebek

39. sor
A nyomógomb hozzáadása az ablakhoz.
4157 sor
A létrehozott widgetek megjelenítése.
43. sor
Belépés az eseményvezérelt szakaszba.

Fentiek ismeretében nagy biztonsággal jósolhatjuk meg példaprogramunk működését. Az elindított alkalmazás egy ablakot jelenít meg, melyben egy Helló Window! feliratú gomb lesz. Az ablak bezárásával hiába próbálkozunk egér, vagy billentyűzet segítségével, ezen kísérletek eredmény csupán egy-egy ”delete event occurred“ sor a konzolban. Ha azonban le találnák nyomni gombunkat az ablak hirtelen eltűnik a konzolban egy ”Helló Window!“ felirat jelenik meg és a program kilép. Lássuk, hogy érhetünk ehhez teljesen hasonló funkcionalitást C++-ban.

Szignálok a gtkmm nyelvű kódban

  1. #include <gtkmm.h>
  2. #include <iostream>
  3.  
  4. void on_button_clicked(const Glib::ustring &hello_msg)
  5. {
  6. std::cout << hello_msg << std::endl;
  7. }
  8.  
  9. class MyWindow : public Gtk::Window
  10. {
  11. protected:
  12. virtual bool on_delete_event(GdkEventAny *event)
  13.  
  14.  
  15. {
  16. std::cout << "delete event occurred" << event << std::endl;
  17. return true;
  18. }
  19.  
  20. public:
  21. MyWindow() :
  22. button("Hello Window!")
  23. {
  24.  
  25.  
  26.  
  27.  
  28.  
  29.  
  30.  
  31.  
  32. button.signal_clicked().connect(
  33. sigc::bind(sigc::ptr_fun(on_button_clicked),
  34. "Hello Window!"));
  35. button.signal_clicked().connect(
  36. sigc::mem_fun(*this, &MyWindow::hide));
  37.  
  38.  
  39. add(button);
  40.  
  41. button.show();
  42.  
  43.  
  44. }
  45.  
  46. private:
  47. Gtk::Button button;
  48. };
  49.  
  50. int main(int argc, char *argv[])
  51. {
  52.  
  53.  
  54. Gtk::Main kit(argc, argv);
  55.  
  56. MyWindow window;
  57.  
  58.  
  59. Gtk::Main::run(window);
  60.  
  61. return 0;
  62. }

Szignálkezelő függvények felkötése

5062 sor
Ahogy azt az általánosságokat taglaló részben említettük Gtk::Window helyett MyWindow típust használunk főablakunk létrehozásához. Mivel azonban a MyWindow publikusan származik a Gtk::Window típusból ez a gtkmm számára nem jelent különbséget. A C változathoz képest a származtatás itt nem csupán ”logikai”, vagyis minden a C++-an megszokott előny könnyedén realizálható. Erre példa, hogy a származtatás miatt nincs szükség semmilyen típuskényszerítésre mikor a Gtk::Main::run függvényt hívjuk, ami pedig egy Gtk::Window referenciát vesz át paraméterként.
2144 sor
Saját osztályunk konstruktorában megtehetjük mindazokat a lépéseket, melyeket a C nyelvű változat esetén a my_window_new függvényben implementáltunk. Úgy is mint a szignálok felkötése, a gomb hozzáadása az ablakhoz, a widgetek megjelenítése. Az egységbezárás ezen előnyén túl a származtatásból fakadó örömöket is élvezhetjük, ugyanakkor persze az ebből fakadó kötelességeknek is eleget kell tenni. Ez esetben ez a konstruktor meghívását jelenti, ami rejtett módon megy végbe. Az ősosztály konstruktorának explicit hívásának hiányában a Gtk::Window azon konstruktora fut le, ami paraméterek nélkül is hívható. Másrészről viszont az adattagként tárolt GtkButtont (button) is inicializálnunk kell. Itt is lehetne közvetve, implicit módon hívni a paraméter nélküli konstruktort, azonban kézenfekvőbb azt a változatot használni, amivel egyszerre a gomb feliratát (label) is megadhatjuk, így egy hívás a későbbiekben megspórolható.
Külön szót érdemelnek a szignálok bekötései. Különösebb programozó géniusz nem kell, hogy felfedezzük a szignálok eléréséhez egy signal_szignálnév szerkezetű hívását használjuk fel. Az ilyen hívások egy, a Glib::SignalProxyBase osztályból származó objektumot adnak vissza, amik connect nevű metódusai valósítják meg azt, amit a GTK+ esetén a g_signal_connect makró tett meg, vagyis egy adott widget, adott szignáljára eseménykezelő felkötését. Előnye ennek a módszernek, hogy típusbiztos, azaz a connect paraméterként csak olyan függvényt (slot) fogad el, melynek típusa megfelel az adott szignálnál leírtakkal. További előny, hogy a slotokhoz nem csupán egy user data csatolható, hanem tetszés szerinti számú, s ezek típusa is ellenőrzésre kerül fordításkor. Amennyiben azonban sikerül csupán egy apróságot is elírnunk a szignál bekötésénél, vagy a slot típusának megadásánál – a sablonokkal (template) történő megvalósításnak hála –, akkor jellemzően több oldalas, nehezen kibogarászható hibaüzenettel találhatjuk szemben magunkat.
3235 sor
Lássuk akkor miként is érhető el ugyanaz gtkmm esetén, mint ami korábban GTK+ használatával. Első pillantásra is szembeszökő, hogy mindkét sorban találunk olyan hívást, amik nem a Gtk névtérben definiáltak. Ennek az az oka, hogy a gtkmm a szignálkezelést egy külső – libsigc++nevű – függvénykönyvtárral valósítja meg. A két eseménykezelő felkötése közötti különbséget az eseménykezelő függvények típusa adja lássuk ezt részletesebben.
32. sor
Ha a megadni kívánt függvény nem kötődik objektumhoz – legyen ez egy osztály statikus tagfüggvénye, vagy akár egy tisztán C nyelvű kódból származó függvény – slot létrehozásához a sigc::ptr_fun alkalmazandó. Ebben a konkrét esetben a slot létrehozásán túl, paramétereket is hozzákapcsolunk a clicked esemény bekövetkeztekor meghívandó függvényhez. Ennek eszköze a sigc::bind, melynek első paramétere egy slot, a továbbiak pedig a csatolandó paraméterek. Itt csupán egy ilyen van, a gomb lenyomásának hatására kiírandó üzenet szövege. Ez persze kissé kényszeredett, hiszen a paraméter értéke soha nem változik, így ennek igazi hasznát ezen a példa alapján még nehéz belátni.
4. sor
Eseménykezelő függvényünk a lehető legegyszerűbb, csupán azt szemlélteti miként is kell az átadott paramétereket használni. Ez esetben annak értékét a standard kimenetre kiírni. Működését és funkcióját tekintve a C-s változat azonos nevű függvényével analóg.
35. sor
Ha a megadni kívánt eseménykezelő egy osztály tagfüggvénye, akkor a sigc::mem_fun használható arra, hogy slotot hozzunk létre az osztály egy példányából, illetve az osztály tagfüggvényéből, ebben a sorrendben átadva őket a függvénynek, utóbbit a teljes névtérlistával együtt. Természetesen az imént említett sigc::bind, az előbbiekhez hasonlóan módon itt is alkalmazható.
Ez a hívás épp ugyanazt a célt szolgálja, mint a GTK+ változat azonos sorszámú sora, azaz hogy az alkalmazásunkból annak ellenére is ki lehessen lépni, hogy az ablak bezáró gombjának hatására a itt sem történik semmi egyéb, mint kiíródik a ”delete event occurred“ szöveg a standard kimenetre. Míg legutóbb a gtk_widget_destroy függvényt kötöttük fel eseménykezelőként, itt a Gtk::Widget osztály hide függvényét használjuk, aminek hatására a GTK főciklusa kilép, mivel annak futtatásakor (??. sor) megadtuk ablakunkat paraméterként. Átgondolva a működést jogosnak tekinthető, hiszen látható főablak hiányában a további működésnek nem sok értelme van, ugyanakkor a C változat gtk_widget_destroy függvénye helyett a C++ változatban a delete hívással lehetne úgymond jelezni, hogy az ablakunkra nincs tovább szükség, viszont ez nem célszerű, hiszen az a main függvényben egy lokális változó.

A delete-event szignál blokkolása

12. sor

A GTK+ változathoz képesti komoly különbség, hogy itt a delete-event szignál blokkolása nem egy felkötött eseménykezelőn keresztül valósul meg – ezért is nincs a kódban olyan sor, ami erre a szignálra vonatkozna –, hanem az alapértelmezett eseménykezelő kerül felülírásra. A működés megértésének kulcsa a virtual kulcsszóban rejlik. Minden szignálhoz tartozik ugyanis egy – az adott widget által implementált – alapértelmezett eseménykezelő függvény, ami alkalmasint felülbírálható (ovverride). Ha ezt megtesszük, azzal a szignál kezelésének teljes folyamatát mi irányítjuk, ami mellett komoly érvek szólhatnak, de nem árt körültekintőnek lenni. Ám ebben az esetben a cél pont annak a demonstrálása, hogy a gtkmm szabad kezet ad abban, hogy egy származtatott widget miként kívánja kezelni az ősosztály eseményeit. A visszatérési érték szerepe ugyanaz, így a működés is azonos az előző – GTK+ nyelvű – példáéval.

A szignálkezelésről összegzésképpen annyit, hogy alapvetően két lehetőség kínálkozik arra, hogy az egyes widgetek eseményei kezeljük:

  • Callbackeket kapcsolni azon widgetek azon eseményihez, melyek számunkra érdekesek és ezekben megtenni a megfelelő lépéseket
  • Felülbírálni a widget saját eseménykezelőjét az öröklődés mechanizmusai útján. Erre mindkét változat esetén van lehetőség, ám a GTK+ megoldása kissé körülményes és nehezebben megérthető, így annak ismertetése valmely későbbi részre marad. A C++ nyelvi eszközeit kihasználva a gtkmm viszont ezt oly könnyedén oldja meg, hogy kár lett volna kihagyni a bemutatást annak ellenére is, hogy a módszerre ritkán van szükség, hiszen többnyire arról van szó, hogy a különböző widgetpéldányok azonos szignáljainak kiváltódásakor más-más irányba szeretnénk terelni a program futását. A felülbírálás révén viszont arra nyílik lehetőség, hogy a szignál kezelésének módját változtassuk meg. Ha nem kívánunk egyebet tenni, mint ami amúgy is történne, hívjuk meg a felülbírált függvény szülőosztálybeli változatát. Ha azonban ez előtt, vagy után még valami másra is szükségünk van, megtehetjük, hogy csak a függvény közepéről hívjuk a szülő metódusát, vagy akár el is hagyhatjuk az ha tudjuk mit és főként hogyan szeretnénk kezelni.

Python nyelvű változatok

  1. from gi.repository import Gtk
  2.  
  3. def on_button_clicked(button, hello_msg):
  4. print(hello_msg)
  5.  
  6.  
  7. def on_delete_event(my_window, event):
  8. print("delete event occurred")
  9. return True
  10.  
  11. def my_window_new():
  12. my_window = Gtk.Window()
  13. my_window.connect("destroy", Gtk.main_quit)
  14. my_window.connect("delete-event", on_delete_event)
  15.  
  16. button = Gtk.Button("Hello Window")
  17. button.connect("clicked", on_button_clicked, "Hello Window")
  18. button.connect_object("clicked", Gtk.Widget.destroy, my_window)
  19. my_window.add(button)
  20.  
  21. return my_window
  22.  
  23.  
  24. if __name__ == "__main__":
  25.  
  26. my_window = my_window_new()
  27. my_window.show_all()
  28.  
  29. Gtk.main()

Elöljáróban annyit érdemes megjegyezni a Python változat kapcsán, hogy az itt alkalmazott megoldások a C, illetve C++ változatból már ismertek, logikájuk hol a GTK+, hol pedig a gtkmm megoldásaira emlékeztetnek – függően természetesen attól is, hogy kihasználjuk-e a Python nyelv adta objektum-orientáltság lehetőségeit –, ötvözve azok előnyeit. Kihasználva a korábbiakban leírtakat, itt már csak a kifejezett nyelv specifikus részeket ismertetjük, a csupán szintaktikai elemekben eltérő részletekre nem térünk ki.

Általánosságok

1. sor
A Gtk szimbólumok importálása, csakúgy, mint a korábbi, minimális példában.
2429 sor
Ez a rész gyakorlatilag azonos a korábbi, minimális példánál ismertetettekkel, azzal a különbséggel, hogy a delete-event szignál helyet az ablakunk destroy szignáljára kötjük rá a programból való kilépéshez vezető main_quit függvényt, melynek magyarázat épp az, mint a korábban C változatnál, erről azonban még esik szó.

A delete-event szignál blokkolása

14. sor

A korábbiakhoz hasonlóan a delete-event szignál elnyomására – függően attól, hogy objektum-orientált megközelítést alkalmazunk-e vagy sem – két lehetőség kínálkozik. Vagy egy szignálkezelő függvényt kötünk fel, amiben logikai igaz értékkel (True) térünk vissza, azt mondva ezzel a GTK szignált feldolgozó kódjának, hogy ezt az eseményt kezeltük, vagy saját osztályunkban írjuk felül az alapértelmezett eseménykezelőt (do_delete_event), amiben hasonlóképpen járunk el. Mivel a Python nyelvben gyakorlatilag minden függvény virtuális ezt minden további nélkül megtehetjük.
7. sor
A kezelő függvény a nyelvi különbségektől eltekintve teljesen azonos a C, illetve C++ nyelvű változatokéval, vagyis mindkét függvény paraméterként megkapja a kezelt eseményt leíró adatstruktúrát, illetve azt az objektumot amin az esemény kiváltódott. Kicsit korrektebbül fogalmazva az objektum-orientált változat valójában azon objektumra kap referenciát, melynek osztályához a kezelő függvény tartozik, de ebben az esetben ez a kettő egybeesik.

Gomb lenyomásának kezelése

17. sor
Mivel a Python esetén a widgetek – a C++ változathoz hasonlóan – valódi objektumok, az eseménykezelő függvények bekötése az objektumon keresztül történik. Az eseménykezelő függvények első paramétere maga az objektum lesz. A szignál bekötésekor megadott további paraméterek a kezelő függvénynek adódnak át. A C változattal ellentétben – ahol csak egy paraméter adható meg – a Python képes egyszerre több paraméter átadására is – éppúgy, mint a gtkmm – ugyanakkor itt természetesen olyan szigorúan vett típusellenőrzésről nem beszélhetünk, mint a C++ nyelv esetén.

Az ablak bezárása

18. sor
Az ablak bezárásának logikája azonos a C változatnál elmondottakkal, vagyis az ablak destroy szignáljának kezelőjeként a main_quit függvényt adjuk meg, azaz az esemény bekövetkeztekor a Gtkfőciklusa, egyszersmind a programunk is kilép.
13. sor
Ezt úgy érjük el, hogy gombunk clicked szignáljának kiváltására meghíjuk az ablak destroy függvényét. Hasonlóan a C változathoz, itt is alkalmazunk némi cselt. Mivel ott csak egy paraméter adható át – pontosabban kettő, hiszen az első maga az objektum, aminek a szignáljára a kezelőfüggvényt felkötöttük –, a paramétereket meg kell fordítanunk, hogy az általunk megadott adat kerüljön az első helyre, vagyis ez legyen a gtk_widget_destroy által megkapott egyetlen paraméter. Voltaképpen a connect_object ugyanezt a célt szolgálja, így ellentétben a connect függvénnyel ennek csak egy paramétere van, mely ez esetben maga az ablakunk, amit a destroy függvénynek adunk át.

Fordítás és futtatás

A korábbiakhoz hasonlóan az alábbi parancssorok segítségével fordíthatóak elemzett programjaink:

gcc #1 -o #2 `pkg-config --cflags --libs gtk+-3.0`
g++ #3 -o #4 `pkg-config --cflags --libs gtkmm-3.0`

Próbálkozzunk ezúttal a ./gtk_signal, ./gtkmm_signal, illetve python gtk_signal.py parancsokkal abban a könyvtárban, ahol a fordítást elkövettük, illetve a Python forrást tartjuk.

Eredmény

Bármily hihetetlen ezúttal sem történik sok egyéb, mint a korábbi minimális példa esetén. A különbség remélhetőleg annyi, hogy a meglepetéssel teli borzongást legutóbb ablakunk váratlan felbukkanása, míg most a bennünk szikraként felvillanó megértés okozza.

 


A Helló Window! cikksorozat könyv formába szerkesztve a Scribd online könyvtárban érhető el.
A mű létrejöttét a FSF.hu Alapítvány a Szabad Szoftver Pályázat keretében támogatta.

[.] More

Helló Window: Első ablakunk

kedd, december 18, 2012 @ 09:12 DE. Author: Pfeiffer Szilárd

A sorozat ezen részében egy – csak a minimálisan szükséges kódsorokat tartalmazó – mintapéldán keresztül a GTK programozás különböző nyelvi változatainak azon területeit vesszük sorra, melyek nélkül rövid távon el lehet boldogulni, de nélkülözhetetlenek az alapos megértést igénylő feladatok megoldásához, amit a teszteléshez szükséges alapismeretekkel fejelünk meg.

Kódolási alapismeretek

A nagyobb nyílt forrású projektek a kódolás, kódszervezés során egy meghatározott konvenciót követnek, bár egy olyan méretű projekt esetén, mint a GNOME az egyes részterületeken lehetnek eltérések, azzal együtt is, hogy az azonosságok nyilván erős többségben vannak. Lássuk mik ezek a GNOME, illetve a GTK projektek esetén.

Forráskód formázása

A GTK fejlesztői a GNU coding standard, illetve a Linux kernel coding style irányelveit alkalmazzák, ami mindaddig csak az olvasást, megértését elősegítő módszertani eszköz, amíg nem áll szándékunkban a GTK fejlesztésébe, javításába belefogni, ugyanakkor két oknál fogva mégis érdemes megemlíteni. Ha még nem ismerkedtünk meg egyetlen kódolási konvencióval sem, akkor az említett kettő – mind népszerűségük, mind letisztult mivoltuk okán – alkalmas választás lehet. A másik ok, hogy néhány a fejlesztés során hasznos információ ezen konvenciókból következik.

Elnevezési konvenciók

A GNOME projekten belül nem csak formázási, de elnevezési konvenciók is használatosak. Ezek az egyes funkciót megvalósító szoftverelemekre (pl: függvények, makrók, …) vonatkoznak, amik közül a az alábbiak már a legegyszerűbb példák esetén is feltűnnek.

  • az egyes nevek részekre oszthatóak,
  • a részek meghatározott sorrendben követik egymást, ahol
    • kezdve a GNOME megfelelő projektjével (pl: atk, gtk, …),
    • folytatva a vonatkozó osztály nevével (pl: entry, label, …),
    • befejezve a megvalósított művelettel (pl: get, set, …),
    • illetve a művelet tárgyával (pl: text, value, …),
  • a részeket aláhúzásjel (’_’) választja el,
  • az egyes részek rendszerint vagy csak kis-, vagy csak nagybetűket, illetve számokat tartalmaznak.

Fentieknek megfelelően egy – a GTK+ által implementált – rádiógomb aktív mivoltát lekérdező függvény neve a gtk előtaggal kezdődik, amit a osztálynév, vagyis a radio_button, majd lekérdezésről lévén szó get akciónév követ, végül pedig a tulajdonság neve (actvie) zár. Aláhúzás jelekkel összefűzve gtk_radio_button_get_active.

A C++, illetve a Python nyelvű változatok esetén is hasonló az elnevezés módszertana a nyelvből fakadó sajátosságok okozta eltéréssel természetesen. A gtkmm esetén a projektek nevét tartalmazó prefixum szerepét a Gtk névtér veszi át, míg az osztályok neve a C++ osztályok neve lesz. A tagfüggvények az előbbi két előtag (pl: gtk_entry) nélküli nevek lesznek (pl: set_text). A PyGobject esetén a névtér szerepét a Gtk modulnév veszi át, a Python osztályok nevei ugyanazt a szerepet töltik be, mint a C++ esetén.

Fejlécfájlok és importálás

 

Szakítva az előző főverziónál (2.x) megszokottaktól az új főverzió (3.x) esetén, a C, illetve a C++ változat egyaránt csak egyetlen állomány beszerkesztésére (include) van lehetőség és szükség. A korábbiakban az egyes widgetekhez tartozó fejlécállományok (pl: gtk/gtkentry.h) beszerkesztésére külön-külön volt lehetőség függően attól, melyekre van, illetve melyekre nincs szükségünk. Most azonban közvetlenül csak a gtk/gtk.h szerkeszthető be, a többi fejlécállomány esetén hibaüzenetet kapunk. Így mind a C, mind a C++ változat azonos módon működik, ami egyébiránt hasonlít a Python modul importjára.

Minimálisan alkalmazás

Ennyi bevezető után lássuk egymás mellett a három nyelvi változat (C, C++, illetve Python) kódját számba véve azok hasonlóságait és különbözőségeit. Ami talán első látásra is feltűnő, hogy messze a GTK+ változat ”kódsűrűsége” a legnagyobb, vagyis a C nyelvű változat igényli ugyanazon funkcionalitás mellett a legtöbb kódsor begépelését, és egyben a legtöbb munkát is. Ez persze nem jelenthet különösebb meglepetést ha van némi tapasztalatunk az interpretált, illetve a fordított, a procedurális, illetve az objektum központú nyelvek esetén elérhető fejlesztési sebesség terén.

Forráskód

Első közelítésben a már említett formai különbségek lehetnek szembeötlőek, ugyanakkor számos, a tartalmat, megvalósítást érintő eltérés is felfedezhető ebben a még oly kevést kódsort tartalmazó példában. Ezek megértése nagyban könnyíti az egyes nyelvi változatok közötti átjárást és ne utolsó sorban a GTK koncepcionális sajátosságaira is rávilágít. Lássuk tehát sorról sorra az imént olvasott kódok magyarázatát.

  1. #include <gtk/gtk.h>
  2.  
  3. int
  4. main(int argc, char *argv[])
  5. {
  6. GtkWidget *window;
  7.  
  8. gtk_init (&argc, &argv);
  9.  
  10. window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
  11. g_signal_connect(G_OBJECT(window), "delete-event",
  12. G_CALLBACK(gtk_main_quit), NULL);
  13. gtk_widget_show (window);
  14.  
  15. gtk_main ();
  16.  
  17. return 0;
  18. }
1. sor
A header fájlok beszerkesztésének különbözőségeiről az előzőekben esett szó, így erre itt csak azt megemlítendő térünk ki, hogy a Python változat import parancsának azt a változatát alkalmazzuk, ami a legerősebb hasonlóságot eredményezi a másik két nyelvi változattal, már ami a gtk kulcsszó forráskódban történő megjelenéseinek helyét illeti.
4. sor
Ez a sor a programjaink belépési pontja, azaz itt kezdődik meg a futtatás, már legalábbis ami a C/C++ változatot illeti. A Python nyelv esetén az import parancs már végrehajtásra került mire ide jutunk, sőt erre a kódsorra voltaképpen nincs is feltétlenül szükséges, leginkább csak a másik két példához való még erősebb hasonlóság végett került be ez a kódsor. Fontossá a main függvények tulajdonképpen csak a parancssori paraméterek GTK-nak történő átadás szempontjából válnak.
6. sor
A C nyelvi verzióban kénytelenek vagyunk blokk elején deklarálni azt a változót – ami ez esetben widgetünk címét tartalmazza majd – mivel az ISO C90 szabvány még nem, majd csak az ISO C99 támogatja a blokkon belül kifejezés után elhelyezett változó deklarációkat. Ezt viszont sem 3.0-nál korábbi GCC, sem pedig a Microsoft Visual Studio C fordítója nem támogatja, vagyis problémába ütköznénk a C++példában használt módszerrel, így a biztonság kedvéért maradunk a hagyományoknál, azaz lokális változók deklarációja csak blokkok kezdetén szerepel. 
8. sor
Eljutottunk végre az első GTK specifikus híváshoz, mely a Python változatból teljesen hiányzik, míg a C, illetve a C++ verzióban funkciójuk azonos, mégis van köztük egy árnyalatnyi különbség. A GTK+ esetén az argc, valamint az argv változók címeit adjuk át, biztosítandó, hogy az init függvény a GTK saját paramétereit 1 el tudja távolítani a tömbből és azok számával csökkenteni tudja argc értékét. Erre a C++-os változat esetén erre azért nincs szükség, mert még ha nem is látszik, mindkét változóra referencia adódik át a Gtk::Main konstruktorának. A Python változat esetén nincs init jellegű függvényhívás, mivel az inicializálás a modul (gi.repository.Gtk) importálásával implicit módon megtörténik, másrészről a parancssori paraméterek a sysmodulon keresztül bárhol hozzáférhetőek, azok paraméterként való átadása így felesleges. 
10. sor
Első widgetünk létrehozása a már említett nevezéktan szerinti függvények meghívásával történik. Minden widgettípushoz létezik egy, mondjuk úgy konstruktor, melyet meghívva egy új – az adott típushoz tartozó – widgetet kapunk vissza. A hívás mikéntje természetesen függ a nyelvtől magától, illetve attól is, hogy a nyelv esetén használható-e, illetve használjuk-e az objektumok-orientált megközelítést. A C nyelvi változat esetén a gtk_widgettípusnév_new forma használatos, addig a C++ esetében a prefixek szerepét a névterek veszik át, tehát általános formában a Gtk::WidgetTípusNév::WidgetTípusNév írható le a konstruktor. A Python szintén névterekkel operál, ahol azok határait a modulok jelentik, melyek neveit egymástól, illetve függvényeiktől pont (.) választja el, vagyis a konstruktor Gtk.WidgetTípusNévformában írható le.
A különbség nem is annyira a nevekben, mint inkább a memória kezelésében rejlik, hiszen egy újólag létrehozott objektum felszabadításáról C esetén magunknak kell gondoskodnunk, míg a C++ a tőle megszokott módon felszabadítja a lokális változókat. Ugyanakkor érdemes itt visszautalni a korábbiakban már említett referencia számlálásra, illetve a ”lebegő” referenciára, melyek révén a különböző nyelvi változat esetén mód van arra, hogy csak a legfelső szintű elemről kelljen ebben a tekintetben magunknak gondoskodnunk, a GTKa többi elem memóriakezelését maga menedzseli.Az ablak létrehozásában meg egy különbség fedezhető fel a C, illetve a másik két változat között.Ez pedig a paraméterkezelés mikéntje. Előbbi esetben meg kell mondanunk az ablak típusát (toplevel) – hiszen a C nyelv nem tesz lehetővé a paraméterek esetén alapértelmezett értékét –, míg a másik két esetben erre nincs szükség. Ez a paraméter ugyan mindkét esetben létezik alapértelmezett értékük, pont az, amit a Cváltozat esetén használunk. 
11. sor
Anélkül, hogy a szignálok kezelésének rejtelmeiben elmerülnénk egy gondolatnyi kitérőt érdemes ezen kódsor a kapcsán tenni. Elsőként azt érdemes tisztázni mire szolgál a delete-event szignál. Ez a szignál akkor váltódik ki, amikor az ablakunk felhasználó interakció hatására záródik be. Ez többféle interakciót is jelenthet – operációs rendszertől és ablakkezelőtől függően –, de alapvetően az ablak jobb felső sarkában2 lévő X gomb, vagy az Alt+F4billentyűk lenyomására kell gondolnunk.A C, illetve a Python nyelvű változat ezen esemény bekövetkeztekor a GTK főciklusát szeretné leállítani, ami annak révén ér el, hogy a delete-event szignálra a nyelvi változatnak megfelelő main_quit függvényt köti fel. Figyelembe véve, hogy a delete-event alapértelmezett szignálkezelője megszünteti (destroy) az ablakot, ez az eljárás logikus, lévén egy programnak főablak nélkül nincs igazán sok értelme. A C++ változat viszont ugyanebben a tekintetben látszólag semmilyen lépést sem tesz, ugyanakkor megfigyelhetjük, hogy néhány sorral lejjebb a run függvények paraméterként átadja azt ablakot, ami nagyon hasonló eredményre vezet. Egészen pontosan nem csak az átadott ablak megszűnésekor, de az eltüntetésekor (hide) is ki fog lépni a főciklus. 
13. sor
Ezek után nem érhet meglepetésként bennünket, hogy miért nincs szükség a C++ változat esetén az megjelenítő függvény meghívására, ezt az átadott ablakra a főciklust elindító runfüggvény megteszi, ami logikus is hiszen ha nincs egyetlen látható ablakunk sem, akkor nehéz olyan felhasználó interakció kezdeményezni – legalábbis a felhasználói felületen keresztül –, ami a főciklus kilépését eredményezné. 
15. sor
A hívások a – korábban már részletezett – GTK main loopot indítják, azaz itt kezdődik meg az az eseményvezérelt szakasz, mely a választott nyelvtől függően a gtk_main_quit, a Gtk::Main::quit, vagy a Gtk.main_quit meghívásáig tart. Ezekben a minimális példákban erre az egyetlen mód a futtatáskor megjelenő ablak bezárása, hiszen az imént említett függvényeket az ennek hatására kiváltódó delete-event szignálhoz rendeltük. Miután a szignált ”kezelő” függvény lefutott a GTK főciklusa (main loop) kilép, azaz a run függvény futása befejeződik, a program futtatása az azt követő soron folytatódhat.
17. sor
Visszatérési értékünk mindhárom esetben 0, amit a rendelkezésre álló nyelvi módszerek legegyszerűbbikével érünk el, ezzel jelezvén a hívó félnek, hogy a futás rendben lezajlott.

Fordítás és futtatás

A korábbiakban már említett metodika mellet a mostani forráskódok3 fordítása a követlezőképp néz ki:

gcc gtk_minimal.c -o gtk_minimal `pkg-config --cflags --libs gtk+-3.0`
g++ gtkmm_minimal.cc -o gtkmm_minimal `pkg-config --cflags --libs gtkmm-3.0`

A futtatás tekintetében próbálkozzunk a ./gtk_minimal, illetve a ./gtkmm_minimal, illetve a python3 gtk_minimal.py parancsokkal abban a könyvtárban, ahol a forrásállományaink is találhatóak.

Eredmény

Nagy meglepetést nem várhatunk egy ilyen méretű applikációtól, viszont az azért kell tudnunk értékelni, hogy a legrosszabb esetben is alig másfél tucat kódsorból egy működő grafikus felhasználó felületet lehet létrehozni, melynek eredménye az alábbi képeken látható.

1. ábra. Minimális mintapéldák képernyőképei C, C++, Python változat esetén

Tulajdonképpen csak egy puszta ablakot kapunk, bármilyen gomb, vagy egyéb elem nélkül. Minden díszítés – az ablak fejléce, a címsor szövege, a minimalizáló, maximalizáló és a bezáró gombok – egyaránt az ablakkezelőnek és nem a GTK-nak köszönhetőek. Ez utóbbi gombhoz kötődik az egyetlen – GTK szempontjából is érdemleges4 – művelet, ami a már több ízben is említett delete-event szignált fogja kiváltani, ami a végső soron a program futásának befejezéséhez vezet.

Tesztelés

Elsőre talán azt gondolhatjuk, hogy a fenti ablakon igazán nincs mit tesztelni, egy feladat mégis akad, mégpedig az, hogy a futó applikációt, illetve annak egyetlen ablakát megtaláljuk, majd bezárjuk, ami mint látni fogjuk azért ez is jelent némi feladatot.

Forráskód

Az ablakok tesztelésére szolgáló programok forráskódjai mindössze két sorban térnek el egymástól, ami a gyakorlatban nem jelent érdemi különbséget, ugyanakkor rámutat arra, hogy a Dogtail használatával két API is rendelkezésünkre áll az applikációk teszteléséhez. Ezek a tree, illetve a procedural API. Előbbi egy objektumorientált megközelítést alkalmazva teszi lehetővé, hogy a felhasználó felület egyes elemeit – gombok, ablakok, menük – elérjük, azokon műveleteket végezzünk, illetve a köztük fennálló összefüggéseket feltárjuk. Utóbbi az ablakokra, illetve azok elemeire – melyeket nevükkel hivatkozhatunk – ad fókuszt, illetve végez egyéb műveleteket, aminek révén egyszerűen vezérelhetjük a tesztelendő alkalmazást.
A következő kód – a fejlesztési példától némiképpen eltérően – nem szorítkozik a minimálisan szükséges kódsorok ismertetésére, ennél egy kicsit tovább megy. Ennek oka kettős. Egyrészről minimálisan alig néhány sorra van szükség, másrészről igyekszünk egy, a valós életben is használható kóddal szolgálni, aminek része egy tesztelési kertrendszer (framework), ebben az esetben a Python unittest modulja. Nem mellesleg a Dogtail saját tesztjei is ezt a modult alkalmazzák, a következőkben ismertetettekhez nagyon hasonló, esetenként teljesen azonos módon. Ahogy a korábbiakban, úgy itt sem ismertetjük a nyelvi eszközökből adódó sajátosságokat, hacsak annak nincs kifejezett hatása a tesztelésre, így a unittest modul is csak olyan mértékben kerül ismertetésre, amennyire az a megértés szempontjából szükséges. Tesztelendő alkalmazásnak a GTK demó programját választottuk, ami csaknem minden widgetre ad példát és része azon fejlesztői korábban már említett csomagnak, mely a C nyelvű fordításhoz szükséges.

  1. from dogtail import tree
  2.  
  3. import unittest
  4.  
  5. class GtkDemoTest(unittest.TestCase):
  6. def setUp(self):
  7. from dogtail import utils
  8. self.pid = utils.run('gtk3-demo')
  9. self.app = tree.root.application('gtk3-demo')
  10.  
  11. def tearDown(self):
  12. import os, signal, time
  13. os.kill(self.pid, signal.SIGTERM)
  14. time.sleep(0.5)
  15.  
  16. def testGtkDemo(self):
  17. pass
  18.  
  19. def testSearch(self):
  20. print(type(self.app))
  21.  
  22. if __name__ == '__main__':
  23. unittest.main()
1. sor
A két különbözőséget adó sor egyike, ahol az Dogtail előbbiekben említett procedural, illetve treemoduljait importáljuk. Az egyes példákban az importált modulnak megfelelő módszereket, illetve eszközöket alkalmazzuk. 
5. sor
A tesztelésre szolgáló osztályunk a Python beépített unittest nevű moduljának TestCase osztályából származik, vagyis ezen modult használjuk a tesztek implementálására. Maga a modul természetesen nem feltétlenül szükséges a Dogtailalapú teszteléshez, ugyanakkor számos olyan eszközt nyújt melynek a későbbiekben még hasznát látjuk.
6. sor
A unittest modul TestCase osztályából származó osztályok setUp, illetve tearDown nevű függvényei a tesztfuttatása során automatikusan meghívódnak minden egyes teszteset előtt, illetve után, lehetőséget adva a összes tesztesetre nézve közös előkészítő, illetve utómunkák elvégzésére. Ebben az esetben az előkészíts nem áll másból, mint hogy a utils modul megfelelő függvényének segítségével elindítjuk a GTK+demó alkalmazását, amit az egyes tesztelési feladatok bemutatására használunk majd fel. 
8. sor
Itt történik a GTK+ demó alkalmazásának tényleges futtatása, ahol a visszatérési értékként kapott folyamatazonosítót (PID) eltesszük későbbi használatra. 
11. sor
Ahogy arról szó esett a setUp függvényhez hasonlóan egy speciális függvény, amit Python unittestmodulja automatikusan hív meg, minden egyes teszteset lefutását követően. 
13. sor
A korábban elmentett folyamatazonosítót felhasználva küldünk kilépésre (termination) felszólító szignált a GTK+demó programjának, mivel annak főablaka nem tartalmaz olyan elemet (pl: menüpont, gomb, …), melynek segítségével a kilépést el lehetne érni. 
14. sor
Levezetésként 5 másodperc várakozás következik minden tesztesetet követően elkerülendő az AT-SPI túlterhelését.
19. sor
Ez a függvény voltaképpen csak demonstrációs jelleggel kapott helyet ebben a példaprogramban, bemutatandó, hogy a nevükben test előtaggal rendelkező függvényeket a Python unittestmodulja tesztnek tekinti és ennek megfelelően futtatja őket. 
23. sor
A unittest modul main függvénye példányosítja a TestCase osztály leszármazottjait – azaz a teszteset implementációkat tartalmazó objektumokat hoz létre –, majd futtatja az azokban lévő – az előbbiekben említett nevezéktan szerinti – tesztfüggvényeket. Bizonyos körülmények megfelelően – például, hogy a függvények futása során keletkezett-e kivétel – hibásnak, vagy sikeresnek tekinti e teszteket. A unittest modul számos funkció révén könnyíti meg a tesztelői munkát, melynek részleteiről a modul dokumentációjában olvashatunk, illetve a további részekben lesz szó.

Futtatás

Az imént ismertetett tesztfuttatása rendkívül egyszerű eredményre vezet, lévén mindösszesen egy tesztesetet (GtkDemoTest) és azon belül is csak egyetlen tesztet (testGtkDemo) tartalmaz, mely reményeink szerint sikeresen fut majd le. A szkript futtatása még ebben az egyszerű esetben is többféleképp lehetséges, már ami a megadható paramétereket illeti. A paraméterek nélkül a szkript az összes tesztesetének összes tesztfüggvényét futtatja, ugyanakkor lehetőség van paraméterként egy teszteset, vagy akár egy azon belüli tesztfüggvény megadására is. Előbbi révén a megadott teszteset összes tesztfüggvénye futtatható, míg az utóbbi eset egy konkrét tesztfüggvény futtatására használható.

A tesztesetek lefutásának mikéntjéről maga a teszt szolgáltat információt futtatáskor megjelenítve az összes futtatott teszteset számát, a futtatás idejét, a sikeresen, a sikertelenül, illetve a hibásan lefutott teszteket. Utóbbi két esetben a hiba okával, illetve a hozzájuk tartozó híváslistával (backtrace) együtt, ami alapjául szolgálhat a hibakeresésnek.

 


A Helló Window! cikksorozat könyv formába szerkesztve a Scribd online könyvtárban érhető el.
A mű létrejöttét a FSF.hu Alapítvány a Szabad Szoftver Pályázat keretében támogatta.

[.] More

Helló Window: A fejlesztés menete

csütörtök, december 13, 2012 @ 09:12 DE. Author: Pfeiffer Szilárd

Gimp Tool Kit

Beszerzése

A GTK+ beszerzésére alapvetően két módszer kínálkozik. Az egyik megoldás, hogy hagyatkozunk az általunk használt operációs rendszerre és az általa biztosított, vagy legalábbis arra elérhető változatot telepítjük. A másik lehetőség, hogy letöltjük a GTK+ és a függőségek forráskódját és némi nehézséget vállalva ezeket fordítjuk le. Előbbi eset bőségesen megfelel amennyiben még csak most ismerkedünk a GTK+ függvénykönyvtárral, illetve nem akarunk túlságosan belebonyolódni az grafikus alkalmazások fejlesztésébe. Elkerülhetetlenül az utóbbit kell azonban választanunk, ha az átlagnál jobban el szeretnénk merülni a GTK+ rejtelmeiben, ha esetleg hibákat javítanánk, vagy változásokat eszközölnénk magán a grafikus eszközkészleten.

Bináris változat

A GTK változatok beszerzése nem jelent különösebb feladatot, amennyiben valamelyik népszerű Linux disztribúciót használjuk, hiszen azok nagy valószínűséggel már amúgy is telepítve vannak az általunk használt rendszeren, lévén vélhetőleg már használunk ezen eszközök segítségével fejlesztett szoftvereket. A fejlesztéshez, illetve teszteléshez a bináris változatokon túl fejlesztői csomagokra is szükséges lesz, amit rpm, illetve deb alapú disztribúciók esetén rendre az alábbi parancsok kiadásával tehetünk meg:

sudo yum install gtk-devel-package
sudo apt-get install gtk-dev-package

GTK+

A GTK+ fejlécfájlokat és egyéb állományokat – melyekről a későbbiekben (2.1) még részletesebben is esik szó – a Debian/Ubuntu, illetve Fedora rendszereken a libgtk-3-dev, illetve a gtk3-devel csomagok tartalmazzák.

gtkmm

A fenti csomagok természetesen csak a C nyelvű változat – azaz a GTK+ – használatához elegendőek, amennyiben a C++ nyelvet – ezzel együtt a gtkmm függvénykönyvtárat – kívánjuk használni, további csomagokra (libgtkmm-3.0-dev, vagy gtkmm3-devel) is szert kell tennünk.

PyGObject

Amennyiben a GTK alapú fejlesztéssel a Python nyelv révén ismerkednénk a már említett fordított nyelvek helyett, akkor a fenti csomagokat nem, a python-gi, vagy a python-gobject csomagokat viszont be kell szereznünk.

Dogtail

Az automata tesztek készítéséhez szükséges Python függvénykönyvtár – a Dogtail – állományait az azonos nevű csomag tartalmazza, melyek installálása a fentiekhez hasonlóan történik.

Forráskód

A GTK forrásának beszerzésére szintén több módszer kínálkozik. Egyrészről az általunk használt Linux disztribúció biztosít eszközöket forráscsomagok telepítésére. A korábbi deb, illetve rpm alapú rendszerek példájánál maradva ez rendre az alábbiak szerint történik.

apt-get source gtk-src-package
yumloader -source gtk-src-package

Lehetőség van természetesen az egyes verziók letöltésére a GNOMEprojekt weboldaláról is,

wget http://ftp.gnome.org/pub/gnome/sources/gtk+/major.minor/gtk+-major.minor.micro.tar.gz
wget http://ftp.gnome.org/pub/gnome/sources/gtk+/major.minor/gtk+-major.minor.micro.tar.bz2

valamint használhatóak e célra egyes projektek verziókelői is, ha szeretnénk mindig az aktuális forráskóddal dolgozni.

git clone git://git.gnome.org/gtk+

Ha azonban magunk szeretnénk a teljes GTK-t fordítani – legyen szó a C, vagy a C++ nyelvű változatról – számolnunk kell azzal, hogy számos egyéb komponens (GLib, Pango, Cairo, ATK, …) fordítására, illetve az frissítéseket követő újrafordítására válik szükségessé, ami meglehetősen időigényes és fáradságos feladat, amit a Linux disztribúción összeállítói már megtettek helyettünk. Így célszerű kezdetben ezt kihasználni és a saját fordításba csak akkor belekezdeni, ha arra feltétlenül szükségünk.

Fordítása

Amennyiben a korábban említett nehézségek ellenére mégis nekivágunk a GTK saját fordításának, akkor sem vagyunk magunkra hagyva. A GNOME projekt része egy JHBuild elnevezésű szoftver1, amit a GNOME projekt moduljaiból összeállított halmazok – mint amilyen a GTK és annak függőségei – letöltésére, frissítésére, fordítására és fordítás eredményeként létrejött futtatási környezetben való munkára használhatunk.

Mindenekelőtt azonban szükségünk lesz néhány olyan eszközre, amik a Linux alapú rendszereken fordítási feladatok ellátására kvázi szabványnak számítanak. A GNOME – sok más fejlesztési projekthez hasonlóan – az Autotoolst2 használja moduljainak fordításához. Ennek részleteibe nem célunk ezen dokumentum keretében elmerülni, már csak azért sem, mert a JHBuild ezen, fordításhoz szükséges, függőségek telepítését megoldja helyettünk.

jhbuild sanitycheck
jhbuild bootstrap

Az parancs futtatásának hatására ellenőrzésre kerül a konfigurációban megadott könyvtárak írhatósága, a szükséges fordítási eszközök telepített mivolta. Ezt követően kerülhet sor a forráskódok letöltésére, a fordítás előtti konfigurálásra, magára fordításra, valamint az elkészült bináris állományok telepítésére. Ehhez a következő parancsokat használhatjuk.

jhbuild update
jhbuild make

A fordítás végeztével lehetőségünk van az újólag létrejött környezetbe úgymond belépni, vagy közvetlenül parancsokat futtatni. Ez gyakorlatilag ennyit tesz, hogy számos környezeti változó beállításának eredményeként saját a fordítandó alkalmazásaink és a fordítás eredményeként létrejött futtatandó állományok is a az új környezetben létrehozott függvénykönyvtárakat fogják használni a rendszeren található változatuk helyett.

jhbuild run
jhbuild shell

Ennek akkor van leginkább haszna, ha szeretnénk az általunk fejlesztett alkalmazást a rendszeren elérhető GTK változaton kívül a legfrissebb verzión is kipróbálni, GTK valamely modulján szeretnénk változtatni és ennek hatását látni alkalmazásunkra, vagy éppen csak nyomon követnénk a GTK változásaik, aktuális fejlesztéseit még mielőtt azok az általunk használt disztribúcióban is megjelenik.

Saját alkalmazások

Mivel a továbbiakban részletesen foglalkozunk majd a GTK alapú alkalmazások létrehozásával, itt most csak a legfontosabb parancsokat vesszük számba, melyek révén a C, illetve C++ nyelvű forrásfájlokból futtatható bináris állományokat hozhatunk létre.

Fordítás és linkelés

Akár a rendszeren található, akár JHBuild, vagy más eszköz révén létrehozott környezetben lévő GTK változatot is használunk, az alábbi parancssorok segítségével fordíthatóak le forrásfájljaink.

gcc gtk_sourcefile.c -o gtk_binary `pkg-config -cflags -libs gtk+-3.0`
g++ gtkmm_sourcefile.cc -o gtkmm_binary `pkg-config -cflags -libs gtkmm-3.0`

Ahhoz, hogy a GTK+, illetve gtkmm fordítási függőségeit ne magunknak kelljen megadnunk a pkg-config parancsot hívhatjuk segítségül, hogy a GCC részére a megfelelő paramétereket meg tudjuk adni. A --cflags paraméter hatására a fordításhoz, míg a --libs eredményeképp a linkeléshez szükséges opciókat kapjuk vissza. A két ` (backtick) közé zárt. aminek hatására a program kimenete része lesz a fordító parancssorának, amivel pont az kívánt hatást érjük el.

Futtatás

Ezek után már csak az örömteli pillanat van hátra, mikor a két különböző nyelven és függvénykönyvtárral lekódolt teljesen azonos funkciójú programunkat lefuttatjuk a ./gtk_binary, illetve a ./gtkmm_binary paranccsal.

Amennyiben a Python nyelvű változat mellett tesszük le voksunkat a fordítás, mint lépés kimarad, a futtatás történhet közvetlenül (./gtk_script.py), amennyiben van az adott fájlon futtatási jog, vagy a Python interpreternek paraméterként (python gtk_sctipt.py). A továbbiakban ezt az utóbbi sémát követjük.

Ezzel túl is vagyunk azon a rövid áttekintésen ami után már épp ideje nekilátnunk első ablakunk implementálásának, futtatásának és tesztelésének.

 


A Helló Window! cikksorozat könyv formába szerkesztve a Scribd online könyvtárban érhető el.
A mű létrejöttét a FSF.hu Alapítvány a Szabad Szoftver Pályázat keretében támogatta.

[.] More

Helló Window: Alapvető ismeretek

kedd, december 4, 2012 @ 10:12 DE. Author: Pfeiffer Szilárd

A fejlesztés fogalmai

Ebben a fejezetben arra próbálunk mélyebben is rávilágítani, hogy bár a GTK+ ugyan C nyelven íródott, mégis számos az objektum-orientált nyelvek esetén megszokott terminológiát használ, sőt ezeket a nyelvi eszközök adta mértékben meg is valósítja. Ezért az objektum-orientált fejlesztés fogalmait, kifejezéseit joggal használjuk még akkor is, ha GTK+ nyelvű fejlesztésről esik szó.

Azzal együtt, hogy az objektum-orientált mechanizmusokat nyelvi szinten a C nem, csak a C++, illetve a Python támogatja lehetséges ezekkel élni ezen változat esetén is. Lássuk mik lennének ezek és hogyan válik lehetővé alkalmazásuk a GTK+ esetén.

Egységbezárás

Az objektum-orientált alapelvek közül C nyelven is viszonylag jól biztosítható elvről beszélünk. A GTK+ meg is teszi, amit ebben a tekintetben meg lehet. Az adatstruktúrákat – jelen esetben widgeteket – és az azokon műveleteket végző függvényeket a lehetőségekhez mérten egységként kezeli, valamint elrejti őket a külvilág elől.

A GTK+ minden saját makrót/függvényt GTK/gtk prefixszel lát el (a GLib esetén ez pusztán csak egy kis, illetve nagy g betű). Egy adott részterület – például egy widget – saját “névtérrel” is rendelkezhet, azaz újabb prefixet vezethet be1. Ezeket egymástól, illetve a “valódi” funkciót jelölő nevektől _ (aláhúzás) jellel választjuk el. A C++ wrapper esetén – kihasználva a kézenfekvő nyelvi lehetőséget – a prefixek szerepét természetesen a névterek, illetve az osztályok veszik át2.
Ezen prefixelt makrók/függvények első paramétere minden esetben a prefix által meghatározott típusú objektum, elősegítve ezzel is ezen függvények, illetve az általuk kezelt objektumok egy egységként való kezelését. A privát adatok külvilág elöl való elrejtésére a C nyelven erre a célra széles körben alkalmazott átlátszatlan mutatókat3 (opaque pointer) használja a GTK+, ami lehetővé teszi az objektum által használt adatszerkezetek, implementációs módszerek elfedését a publikus interfész használó kódok elöl.

Öröklődés

Megoldott a widgetek egymásból történő származtatása, sőt felhasználói widgetek is definiálhatóak a már meglévőekre támaszkodva. Az kód újrahasznosítását jól mutatja az a tény, hogy a GObject osztály – mely önmagában is számos hasznos funkcióval rendelkezik – minden widget, illetve számos más a GTK-ban használt nem vizuális elemnek őse. Meg kell jegyezni, hogy a gtkmm, illetve a PyGObject esetén – lévén ezen esetekben az objektum-orientáltságot támogatja mag a nyelv – természetesen a származtatás nagyságrenddel egyszerűbb, de mintapéldákat felhasználva némi rutinnal a GTK+ esetén sem igényel különösebb erőfeszítést.

A widgetek öröklési fájáról már itt érdemes megjegyezni, hogy az nem csupán két szintű. A GObject típus a fa csúcspontja, de közte és az egyes widgettípusok között adott esetben még számos csomópont található az öröklési fában. A hasonló funkcionalitású, következésképp rendszerint hasonló megjelenésű widgetek (3) értelemszerűen egymásból származnak. Ez még a leghétköznapibb esetekben is igaz. A számok kezelésére is alkalmas widget (spin button) őse az egyszerű, egy sornyi szöveg befogadására alkalmas beviteli mezőt megvalósító widget (entry).

Egysoros beviteli mező
Számbeviteli mező
Öröklődés hasonló funkciójú widgetek között

A mechanizmus további előnye az interfészek4 kialakításának lehetősége. A GTK+ a 3-as főverziót megelőzően törekedett arra, hogy a különböző widgetek azonos funkciót megvalósító részeit (kattintható, szerkeszthető, görgethető elemek) egységes programozási felületen keresztül érhetjük el, ezt követően ez a tendencia tovább erősödik.

Polimorfizmus

Hasonlóan az öröklődéshez – pusztán nyelvi szinten – itt sem érhető el teljes körű megoldás C nyelv esetén. Ugyanakkor a fő momentum, vagyis a származási hierarchia egyes osztályainak specifikus viselkedése egy adott funkciót megvalósító metódusok tekintetében elérhető. A widgeteket leíró struktúrákban ugyanis a származtatott osztályokból felülírhatóak az egyes funkciókat implementálható függvények mutatói5. Az így létrejövő többalakúság bár közel sem tökéletes, ám számos gyakorlati problémát megold.

Ezen felül a GTK+ minden widgettípushoz – mondhatni osztályhoz – definiál egy-egy makrót, melyek segítségével futásidőben ellenőrizhető egy adott widget, azaz objektum tényleges típusa, hasonlóan ahhoz, amire a dynamic_cast használata ad lehetőséget a C++-ban. Azt a mechanizmust, melynek révén lehetővé válik a GTK+-ban a futás idejű típusellenőrzés, a már említett GObject osztály implementálja, az ebből származó saját osztályainknak nem csupán lehetséges, de szükséges is a használata.

Widget

A fogalmat egyrészt, mint gyűjtőfogalmat használjuk a grafikus felhasználói felületek programozása során a felhasználó felületek egyes grafikai elemeinek megnevezésére6, mint amilyen például egy rádiódomb, egy szövegbeviteli mező, vagy akár egy kép. Másrészről a GtkWidget minden – ez előbbi értelemben vett widget – ősosztálynak a neve is – még ha az származtatás a C esetében nyelvi szinten nem is támogatott – melyből minden egyes elem származik.
A GtkWidget osztály, mint a widget fogalom objektum-orientált leképezése a felhasználó felület egyes elemeinek tulajdonságait, illetve az azokhoz kapcsolódó műveleteket zárja egységbe, biztosítva egyúttal az általa implementált funkciók újrahasznosíthatóságát éppúgy, mint a felülbírálhatóságukat. Lássuk, hogy ezen általánosságban megfogalmazott elvek mögött mi is rejtőzik.

Tulajdonságok

A mindennapi felhasználás – ez esetben ugye a mindennapi szoftverfejlesztés – során talán a leggyakrabban felmerülő kérdés – nyilván csak azt követően, hogy megismerkedtünk milyen tulajdonságokkal bírnak a különböző widgettípusok –, hogy mik ezen tulajdonságok aktuális értéki konkrét objektumaink esetén. Ezen tulajdonságok (property), illetve ezek értékei határozzák meg widgeteink megjelenését, a felhasználói interakciókkal, illetve más widgetekkel összefüggő viselkedését.

Ezek a tulajdonságok lehetnek egészen kézenfekvőek, mint amilyen például egy beviteli mezőben szereplő szöveg, egy folyamatindikátor százalékban vett értéke, egy rádiógomb be-, kikapcsolt állapota. Lehetnek teljesen általánosak, mint amilyen widgetek neve, láthatósága, méretei, a tartalmazó konténerben elfoglalt helyzetük, igazításuk. Tükrözhetnek valamilyen állapotot, mint hogy a widget fókuszban van-e, fogad-e a felhasználói interakciókat és természetesen számos – csak az adott widgettípusra vonatkozó – tulajdonságot.

Szignál

Lévén egy alapvetően eseményvezérelt eszközről beszélünk a fent említett tulajdonságok értékeinél már csak a widget által definiált események (event) bekövetkezéséről, vagy éppen elmaradásáról való értesülés lehet fontosabb. Különösen azon esetekben mikor az esemény számunkra valamilyen szempontból jelentőséggel bírnak, következésképp arra reflektálni szeretnénk. Ez persze nem mindig van így, hiszen mondjuk adatok bevitelére szolgáló ablakban egy rádiógomb állapotának változása nem feltétlenül érdekes, inkább csak annak akkori értéke, mikor az ablakon található nyugtázó gombot (pl: Ok, Alkalmaz, …) lenyomtuk és az ablakban megadott adatoknak megfelelően szeretnénk eljárni. Ellenben ez utóbbi eseményről – mármint hogy a gomb lenyomásra került – csaknem minden esetben értesülni szeretnénk.
A widgetek eseményeinek bekövetkeztéről a GObject osztály által implementált értesítési rendszer, a szignálok (signal), révén áll módunkban tudomást szerezni. Ezen mechanizmus keresztül valósítható meg az eseményekhez kezelőfüggvények (callback) kapcsolása, ahol a felhasználó akcióra a megfelelő reakciót válthatjuk ki. Az imént említett nyugtázó gomb lenyomásának hatására példának okáért bezárhatjuk az ablakot, vagy épp hibaablakot dobhatunk fel, ha a bevitt adatok a validáció során nem bizonyultak helyesnek. Itt érdemes megjegyezni, hogy a GObject nem csupán a GtkWidget osztály őse, hanem más GTK-s elemeknek is, melyek jellemzően nem hagyományos widgetek, ugyanakkor ki szeretnék használni a GObject osztály szolgáltatásait. Az olyan elemeknek is lehetnek tehát szignáljai, melyek nem jelennek meg közvetlenül a felületen, amire egy adattároló (pl: GtkTreeModel, TextBuffer) objektum lehet jó példa. Ezen osztályok példányai is szignálokon keresztül kommunikálnak, így adnak például jelzést arról, hogy a tárolt elemekben változás állt be.

A GTK szignál kulcsszavának jelentése (jel, jelzés) tehát jól tükrözi funkcióját. Minden widgethez tartoz(hat)nak különböző események – mint amilyen egy gomb esetén annak lenyomása (vagy éppen felengedése), egy beviteli mezőnél az abba történő írás – melyekről a widgetek – jellemzően GDK révén, egy alacsony szintű7 esemény formájában – tudomást szereznek, majd elvégzik a megfelelő műveleteket – gomb újrarajzolás, beviteli mezőbe karakter írása a karakter megjelenítése – majd értesítést küldenek a program többi része felé, immár egy magasabb szinten8 értesítést. Ezt az értesítést, avagy jelzést nevezzük szignálnak.

A szignálokhoz alapvetően három tevékenységhez kapcsolódik. Ezek közül kettő leginkább csak a saját widgetek fejlesztése során kerül elő, míg a harmadik gyakorlatilag még a legegyszerűbb esetekben is nélkülözhetetlen. Ez utóbbi a függvények kapcsolása (connect) az eseményekhez, ezt azonban meg kell előzze a a másik két említett művelet. Időrendi sorrendben ez a szignálok regisztrációja register) – ahol meg kell adunk az eseményünk nevét, illetve paramétereit –, illetve a szignálok küldése, kibocsátása (emit), ahol a regisztráció során megadott nevet, valamint paramétereket szükséges megadni. Az eseménykezelők kapcsolásakor ezt a nevet használjuk fel, meghívásukkor pedig ezek a paramétereket kapjuk meg.

Callback

Amennyiben egy adott widgethez kapcsolódó valamilyen eseményről (event) tudomást kívánunk szerezni a program futása során, ezt úgy tehetjük meg, hogy a widget megfelelő típusú jelzéséhez (signal) eseménykezelő függvényt (callback) kapcsolunk. Itt minden olyan művelet elvégezhető, ami nem a widgethez, hanem annak programunkban betöltött szerepéhez kötődik. A korábbi példánál maradva ha egy adatok bevitelére szolgáló ablak Ok gombjának lenyomásánál szükséges lehet a felhasználó által megadott adatok szintaktikai, illetve szemantikai ellenőrzése, az ellenőrzött adatok mentésére, majd az ablak bezárására, probléma esetén hibaablak feldobására, valamint a beviteli folyamat újrakezdésére, akkor azt a kezelő függvényben mind megtehetjük. Egyébiránt az egyes tulajdonságok (property) megváltozása szintén események minősül, vagyis ezekhez is módunk van függvényeket csatolnunk.

Main Loop

Az események kezelése kapcsán megválaszolandó az a triviálisan adódó kérdés, hogy az egyes widgetek hogyan szereznek tudomást a rajtuk – a felhasználók, vagy éppen az automata tesztelő eszközök által – végrehajtott akciókról. A válasz pedig épp ugyanabban rejlik, mint bármely más felhasználói felületek fejlesztésére szolgáló eszközkészlet esetén, azaz az eseményvezérelt működési modellben. Ez nem jelent más mint, hogy a GTK+ mindaddig várakozik, amíg valamilyen forrásból esemény nem érkezik (pl: egér mozgatása, gombjainak lenyomása, billentyű felengedése, …). Amennyiben egy esemény bekövetkezik meghatározza, melyik widgetet érintett az esemény által, meghívja a widget megfelelő eseménykezelő függvényét, majd újabb várakozásba kezd.
Ennek a várakozási ciklusnak az implementációja main loop. A GTK+ tulajdonképpeni főciklusa a Glib függvénykönyvtárban implementált, a GTK+ ezt újra felhasználva ciklizál, várva a grafikus szerver – legyen az a Linux, a Mac OS X, vagy a Windows megfelelő alrendszere – üzeneteire. Mindezt a GDK-n keresztül teszi, mely – mint azt az előző részben is említettük – egy vékony burkoló réteg az ablakozó rendszer köré. A main loop tehát az ami az imént említett kapcsolaton át eljuttatja az ablakozó rendszer alacsony szintű eseményeit a GDK által standardizált formában az egyes widgetekhez, hogy ezek a feldolgozást követően egy magasabb szintű eseményt váltsanak ki a többi widget, illetve az applikáció más részei felé.

Referencia-számlálás

A GTK+ segítségével létrehozott felületek – ahogy azt a későbbiekben látni fogjuk – nem widgetek szórvány halmazát, hanem egymással szoros összefüggésben álló (szülő-gyerek, modell-nézet-vezérlő kapcsolat) elemek hálózatát jelentik. Ennek okán az megoldandó feladat, hogy az egymáshoz valamilyen szempont alapján kötődő elemek egymás oly módon tudják hivatkozni, hogy hivatkozások létrejötte, illetve megszűnése egyúttal a widgetek memória menedzsmentjére is megoldást adjon. Ennek bevett módszere a referenciák tartása a hivatkozott elemekre, mellyel a GTK+ is él.

Minden GObjectből származó osztály – így a GtkWidget is – rendelkezik referencia-számmal, mely tulajdonképpen azt fejezi ki, hogy hányan hivatkoznak az adott elemre. A GTK+ – pontosabban ez esetben a GLib – ”lebegő” referenciát (”floating” reference) alkalmaz, mely azt jelenti, hogy az objektum létrejöttekor annak referenciája 1 lesz, bár a widgetre ekkor még nem hivatkozik semelyik másik widget sem, azaz ezt a referenciát úgymond nem birtokolja senki. Amikor egy widgetre megszületik ez első valódi hivatkozás, például egy konténer osztályba – mint amilyen egy közönséges ablak is – tesszük a widgetet, vagyis létrejön az első valódi hivatkozás az elemre, akkor az a hivatkozó birtokába kerül. A referencia-érték változatlanul 1 marad, viszont a lebegő referencia elsüllyesztésre (sink) kerül. Minden ezt követő esetben a konténerből történő eltávolítás csökkenti, ahhoz való hozzáadás pedig növeli a referencia értékét. Érdemes felhívni a figyelmet arra, hogy az elmondottak alapján, ha hozzáadtuk widgetünket egy konténerhez, majd pedig eltávolítjuk belőle azt, akkor annak referenciája 0-ra csökken, ami maga után vonja a widget destruktorának lefutását. Ezt elkerülhetjük, ha az eltávolítás előtt explicit módon növeljük a referenciát, amit aztán csökkentenünk kell, ha egy másik osztály “birtokába” adjuk a widgetet.

Szülő-gyerek kapcsolat

A szülő-gyerek kapcsolat a már említett konténerek – azaz a GtkContainer, illetve az abból származó osztályok – viszonylatában merül fel. Ezen elemek teszik lehetővé a widgetek felületen való elrendezését (ablakok, táblázatok, gombok, …), egymásba ágyazását. A konténer tehát az a widget, amely további widgetet, vagy widgeteket tartalmazhatnak. Ilyen értelemben egy szülő-gyerek kapcsolatot valósítanak meg, ahol minden szülőnek lehetnek gyermekei, de egy gyermek widgetnek minden esetben csak egy szülője van, vagyis a szülők és a gyerekek egy fa hierarchiát alkotnak.

Ez a szerkezet több szempontból is fontos szerepet játszik a GTK+ működése során. Egyrészről a referencia-számlálásnál már említett módon, azaz ha egy widgetet hozzáadunk egy konténerhez, akkor az úgymond tart rá egy referenciát – vagy a referenciaszám növelésével, vagy ”lebegő” referencia elsüllyesztésével –, majd elereszti azt a konténerből való eltávolításakor. Másrészről egy még nem ismertetett – a szülő- és gyerekwidgetek viszonyának tulajdonságait rögzítő – mechanizmust tesz lehetővé, melyről a későbbiekben még részletesebben esik szó. Elöljáróban csak annyit, a widgetek saját tulajdonságain (property) túl, léteznek olyanok is melyek szülő widgetekkel való kapcsolatára jellemzőek, mint például a gyerek widgetek elhelyezkedése a konténerben (pozíció, térköz, kiterjedés, …).

Interfészek

A GTK+, erősítve az objektum-orientált megközelítést, olyan absztrakciós rétegeket definiál, amiket az adott funkciót (szöveg bevitel, aktiválhatóság, igazítás) betöltő widgetek implementálnak. Az ilyen típusú általánosítások komoly haszonnal bírnak, mikor az adott funkciót egységes felületen keresztül, a konkrét implementáció részleteivel nem törődve, szeretnénk kezelni.

Függetlenül attól, hogy például egy, vagy többsoros beviteli mezőről legyen szó, a karaktereket épp úgy szeretnénk kiolvasni mindkét esetben. Éppúgy igaz ez az elrendezés (orientation) tekintetében, hisz amennyiben a megfelelő widgetek – jellemzően a konténerek – megvalósítják ezt, az elrendezésre vonatkozó interfészt, a vízszintes, illetve függőleges orientáció futás közben is könnyedén váltható (flip). Van egy olyan interfész, amit minden egyes felülettel rendelkező widget (GtkWidget) és számos felülettel nem rendelkező objektum (GObject) is implementál (GtkBuildable). Ezen osztályon keresztül valósulnak meg a legalapvetőbb funkciók, mint amilyenaz objektumok nevének, tulajdonságok értékének lekérdezése, beállítása, a gyerek widgetek létrehozása9.

A tesztelés fogalmai

A tesztelési feladatok ellátása kapcsán a GTK ismerete bizonyos esetekben nem árt, míg más esetekben nem sokat használ. Ez azon egyszerű oknál fogva van így, mivel a tesztelés során a megközelítés merőben eltérő, lévén a teszteléshez használt rendszer nem közvetlenül a GTK+-ra , hanem az ATK-ra épít.

A már említett ATK koncepciójában némiképpen különbözik a GTK+-től. Lévén ezt az interfészt a fogyatékossággal elő emberek szükségleteinek kielégítésére tervezték, elsősorban nem magukra a widgetekre, vagy azok kapcsolataira, felületi megjelenésére, hanem az általuk hordozott információkra koncentrál. Ezen információk rejtőzhetnek természetesen a widgetek által megjelenített szövegekben, vagy számszerű értékekben éppúgy, mint a widgetek aktuális állapotában (aktív, szerkeszthető, látható, …), ugyanakkor persze az egymás közi viszonyok (tartalmazás, vezérlés, …) is meghatározóak lehetnek.

Ezen interfészek, állapotok, illetve viszonyok függetlenek a konkrét implementációtól. Bármely widgetkészlet számára implementálhatóak, sőt implementálandóak, ami annyit tesz, hogy az egyes widgetkészletek logikája még ha nagyjából egyezik is az ATK logikájával, számos ponton kisebb-nagyobb eltérések tapasztalhatóak, amiket szükséges valamilyen, a widgetkészlet és az ATK között elhelyezkedő, azokat összekötő (bridge) implementációval áthidalni.

Interfészek

A kifejezetten a funkcionalitáson alapuló megközelítés egyenes következményei az ATK interfészei. Ezek gyakorlatilag a grafikus felhasználói felület elemeit legfőbb funkcióik szerint csoportosító eszközök. Egy adott GTK widget természetesen megvalósíthat több interfész is, lévén többféle funkciót is betölthet. Egy egyszerű példával megvilágítva a helyzetet egy közönséges gomb (GtkButton) egyszerre valósítja meg a szöveg (AtkText), illetve a képek (AtkImage) lekérdezésére szolgáló interfészeket, hiszen a gombon kép és felirat egyaránt elhelyezhető.

Állapotok

Bizonyos esetekben nem a widget által tartalmazott adat – legyen az szám, vagy szöveg, esetleg kép – hordozza az információt, hanem widget valamilyen szempont szerinti állapota. Általánosságban véve ilyen állapot lehet egy widget láthatósága, egy ablak átméretezhetősége, egy beviteli mező szerkeszthetősége, vagy akár egy rádiógomb aktív mivolta. Az állapotok mindegyike bináris, azaz igaz vagy hamis értékkel írható le. Ennek megfelelően az ATK által definiált állapotok egy bithalmazt alkotnak, melyek leírják egy adott widget konkrét időpillanatban vett állapotát.

Viszonyok

Tesztelési szempontokból létezik még egy jelentőséggel bíró tulajdonságtípus, mely azonban nem konkrét widgetek paramétereit, hanem azok egymáshoz kötődő viszonyát írják le. Úgyis, mint egy felirat (label) és a hozzá kötődő widget összetartozását, a vezérlő és vezérelt widget között fennálló viszonyt, vagy éppen a fák megjelenítésére használt widget esetén a az elemek szülő-gyerek kapcsolatait. Az egyes viszonyok (relation) – hasonlóan az imént említett állapotokhoz – szintén két értékeket vehetnek fel, bár ellentétben azokkal az állapotokkal az egyes viszonyok csak egy másik widgettel együtt van értelmük. Az ellentétes előjelű viszonyok, mint a vezérlő és vezérelt widget egyidejűleg is igazak lehetnek, más-más widgetekkel összefüggésben.

A Dogtail működése

A Dogtail a szoftverek akadálymentesítésének megvalósítására használta technológiai módszereket (assistive technologies) alkalmazza a tesztelendő alkalmazások vezérlésére.

Akadálymentesített szoftverek elérése

A működés modell végeredményben nem túl bonyolult. Az applikáció közvetlenül nem szólítható meg. Ahogy ezt hang alapú vezérlést megvalósító, illetve képernyőolvasó szoftverek is teszik, az AT SPI-n (Assistive Technologies Service Provider Interface) keresztül szólítják meg a megfelelő szoftvert. Amennyiben ez egy GTK+ felhasználásával fejlesztett alkalmazás, akkor a kérésre a Gail nevű alrendszert futtatva válaszol, az ATK interfészben leírtaknak megfelelően.

Ez a mechanizmus adja az alapját az automata tesztelésnek is. Maga a teszt is az AT-SPI interfészt használja arra, hogy a tesztelendő applikációval kommunikáljon, ezen keresztül kérdezi le a korábban említett állapotokat, viszonyokat, illetve szólítja meg az egyes widgetek által implementált interfészeket. Az így vezérelés alá vont szoftver egyes elemeinek állapotát, illetve tulajdonságait követve vonhatóak le következtetések arra nézvést, hogy az adott szoftver a kívánalmaknak megfelelően működik-e.


1
a GtkWindow típushoz tartozó függvények prefixe gtk helyett gtk_window
2
a Window típus a gtkmm esetén Gtk névtéren belül szereplő Window nevű osztály
3
a módszer egyebek mellett pimpl (pointer to implementation idiom) néven is ismert
4
a kifejezés alatt a Java nyelv interfész, illetve a C++ absztrakt osztálya értendő
5
ami hasonló eredményre vezet, mint a C++ virtual kulcsszavának használata esetén
6
ebben az értelemben a ”window gadget” kifejezés rövidítése
7
billentyű lenyomása, felengedése, egérkattintás, …
8
beviteli mező értékének változása, kattintás, …
9
mára a Glade elnevezésű felhasználói felületek tervezésére szolgáló alkalmazás is ezt az interfészt használja

 


A Helló Window! cikksorozat könyv formába szerkesztve a Scribd online könyvtárban érhető el.
A mű létrejöttét a FSF.hu Alapítvány a Szabad Szoftver Pályázat keretében támogatta.

[.] More

Helló Window: GIMP Toolkit

csütörtök, október 11, 2012 @ 09:10 DE. Author: Pfeiffer Szilárd

Általánosságok

Története

Mint megannyi szoftver a nyílt forrás több, mint negyedszázados történetében a GTK+ is felhasználói elégedetlenség eredményeként született. Peter Mattis és Spencer Kimball, a Kaliforniai Egyetem (Berkeley) hallgatói – az azóta is a nyílt forrású szoftverek egyik zászlóshajójának számító képszerkesztő program, a GIMP fejlesztése közben – szembesültek az akkori időkben amúgy is csak kis számban rendelkezésre álló grafikus felhasználói felület készítő eszközök hiányosságival. Ezek leküzdésére döntöttek úgy – egyébiránt a Motif használata helyett –, hogy belekezdenek egy saját függvénykönyvtár fejlesztésébe.

Az azóta nagykorúvá lett GTK+ több ízben is komoly átalakuláson ment keresztül. Ezek közül talán a legmeghatározóbb, hogy az eredetileg GUI eszközkészletként indult projekt jócskán túllépett eredeti keretein. Ennek lehetőségét a moduláris felépítés teremtette meg, mely később még több ízben hasznára vált a projektnek. Azon részek, melyek nem közvetlenül függenek össze a grafikus felhasználói felületek fejlesztésével, vagy másutt is hasznosak lehetnek, külön modulokban kaptak helyet, egy flexibilis eszközkészletet hozva így létre, melyből mindenki pontosan annyit és csak annyit használ fel, amennyire feltétlenül szüksége van.

A legutóbbi főverzió – vagyis a GTK+ 3 –, melyről a továbbiak szó lesz majd, nem csupán folytatja a hagyományokat, de igyekszik mindinkább kiszélesíteni a modularitás adta előnyok alkalmazási területeit.

Elérhetősége

A GTK+ többféle formában, többféle operációs rendszerre és grafikus szerverre is elérhető. A forma alatt ez esetben az értendő, hogy a függvénykönyvtár nem csupán forráskódként, hanem bináris változatban is letölthető. Ez nyilván nem különösebb meglepetés, már csak azért sem, mert nyílt forrású szoftverről van szó. Itt érdemes megjegyezni, hogy a licenc a GTK+ – annak függőségei, illetve számos kapcsolódó függvénykönyvtár esetén – a GNU Lesser General Public License, rövidítve az LGPL, ami összefoglalva annyit tesz, hogy nyílt, illetve zárt forrású szoftverek egyaránt fejleszthetőek a GUI eszközkészlet használatával, azzal a kitétellel, hogy a függvénykönyvtár általunk használt – esetleges módosításokat is tartalmazó – változatának forrását ügyfeleinknek kérésre át kell adnunk. A szokásjog, illetve a saját érdekünk azt diktálja ugyanakkor, hogy a javítások mielőbb bekerüljenek a fejlesztők által karbantartott változatba. Visszatérve az elérhetőséghez a forráskód mind verziókezelő rendszeren (Git) keresztül, mind archív állományok formájában letölthető.

A bináris változatok tekintetében elmondható, hogy mindaddig, amíg GNU/Linux alapú rendszereken dolgozunk, különösebb nehézségekbe nem fogunk ütközni, hiszen nagy valószínűséggel az általunk használt disztribúció mind a fejlesztéshez (devel), mind a hibajavításhoz (debug) szükséges csomagokat tartalmazza. Ugyanakkor a GTK+ egy multiplatform eszköz, vagyis joggal várható el, hogy több operációs rendszeren is működjenek az általunk megírt kódok. A már említett GNU/Linux disztribúciókon túl – melyet a továbbiakban elsődlegesen, de közel sem kizárólagosan használunk majd – a GTK+ mind a Microsoft Windows, mind pedig az Apple Mac OS X rendszerein elérhető.

A használható grafikus szerverekről elöljáróban annyit érdemes megemlíteni, hogy a GTK+ portabilitásának két alappillére közül az első az a megoldás, mely a felhasználó felület építőköveinek – a GTK+ által használt terminológiával élve widget – implementációját elválasztja az azok megjelenítésére szolgáló rajzoló primitívek megvalósításától. Ennek révén biztosítható, hogy a GTK+ forráskód túlnyomó részének változatlansága mellett – csupán egy újabb, úgynevezett backend hozzáadásával – alkalmassá tehető egy újabb grafikus szerver, vagy alrendszer (pl: X11, frame buffer, HTML5, …) alá.

Részegységek

A GTK1 szervezéséről fontos elöljáróban megemlíteni, hogy példaértékűen választja szét a funkcionalitás egyes elemeit, jól elhatárolt implementációs részegységre, melyek fejlesztése egymástól függetlenül, a GNOME projekttel együttműködésben folyik.

Ez a megoldás – számos előnye mellett – jár természetesen néhány nehézséggel is. A modulok csak publikus interfészeiken keresztül tudnak egymással kommunikálni. Ez egyrészről függetlenséget jelent a modulok belügynek tekinthető implementációs részleteinek terén, viszont komoly kötöttség a publikus felület oldalán, lévén annak változatlanságától nem csak a külső projektek, de az egyes GTK modulok is függenek.

A hosszan fenntartandó állandóság hatásait jól példázza, hogy a GTK+ csak hat év után indított új főverziót (3.x), mely felszámolta a korábbi változattal való – helyenként csaknem teljesen felesleges – kompatibilitást. Az azóta eltelt időben2 viszont vajmi kevés projekt döntött úgy, hogy átáll az új verzióra, még azzal együtt sem, hogy az egyszerűbb alkalmazások tekintetében ez különösebben komoly erőforrást nem igényel. Célszerűen tehát ezek az inkompatibilis váltások nem lehetnek túl gyakoriak.

A következőkben sorra vesszük, mik azok a részegységek, melyek együtt a GTK C nyelvű változatát alkotják, s melyek természetesen a további nyelvi változatok alapjául szolgálnak.

GTK

A GTK+ a grafikus felhasználó felületek (GUI) fejlesztéséhez szükséges felületi elemek (beviteli mezők, rádiógombok, listák, dialógus ablakok, …), azaz widgetek tárháza, vagyis a GTK keretrendszer lelke. A függvénykönyvtár a korábban leírtaknak megfelelően csak a közvetlenül szükséges implementációt, vagyis a widgetek kirajzolásához, interakcióinak, adattárolásának megvalósításához szükséges kódok összességét jelenti. Ettől persze némiképp többet, de erről később () esik szó.

GDK

A GIMP Drawing Kit (GDK) – a GTK+ részeként terjesztett – alacsony szintű rajzolási és ablakkezelési feladatok megvalósítására, egyszersmind a magasabb szintű rutinok elöl történő elfedésére szolgáló függvénykönyvtár. A GDK fontos szerepet tölt be a GTK különböző platformok közötti hordozhatóságának megteremtésében, lévén az általa nyújtott – viszonylag szűk körű – funkcionalitást újraimplementálva a GTK+ alkalmas lehet egy újabb grafikus környezetben való futásra. Ezen funkciók alatt a már említett rajzolási primitíveken túl többek között rasztergrafikai feladatok, kurzor megjelenítése, illetve alacsony szintű ablakesemények implementálása értendő.

A fentieknek köszönhető, hogy az eredetileg csak az X Window System3 (X11) felett működni képes GDK, mára nemcsak egyéb Linux alapú szerveren (Wayland), de más operációs rendszereken (Windows, Mac OS X), sőt akár webböngészőben is működni képes4. A grafikus alrendszer portolása mellett felmerülő problémák jelentékeny részét a GLib függvénykönyvtár oldja meg.

GLib

A GLib a GNOME projekt egyik fundamentális eleme, mely történetileg ugyan kötődik a GTK+-hoz, mára azonban teljesen önállóvá vált. Eredendően a GTK+ által használt, de attól függetlenül is létjogosultsággal bíró, platformfüggetlen kódok kiemelése egy külön függvénykönyvtárba, melyet számos – a GNOME, vagy a GTK projektekhez akár nem is kapcsolódó – szoftver5 alkalmaz. A meglehetősen szerteágazó funkcionalitáscsomag melyet a Glib megtestesít, röviden a következőben foglalható össze; C programozási nyelven írt standard függvénykönyvtárak nem, vagy csak részben elérhető, esetleg nehézkesen használható eszközök összessége.

A teljesség igénye nélkül megemlítendőek a GLib adattárolói (láncolt listák, fák, dinamikus tömbök, hash táblák, …), hordozható adattípusai (guint32, gboolean, …), memória allokációs függvényei (g_new, g_allocator_new, g_slice_alloc, …), gyakran használt formátumok (dátum, idő, URI, fájl- és könyvtárnév, …) kezelésére szolgáló eszközei, konverziós algoritmusai (base64, endianness, karakterkódolás, …) széles körben alkalmazott módszerek implementációi (XML, Glob, Regex, …). Mindezeken túl a GLib tartalmaz néhány külön is említésre méltó alrendszert.

GObject

A GLib Object System egy C nyelven írt objektumorientált keretrendszer, mely megvalósít számos olyan funkciót (származtatás, virtuális függvények, típus, objektumok memória menedzsmentje, …) melyek például a C++, vagy a Java esetén nyelvi szinten adottak. A GObject, mint osztály szolgál például alapjául a GTK+ által implementált minden egyes widgetnek. A kép ezzel azonban még közel sem teljes.

A GObject implementál számos alapvető fontosságú egyszerű (gdouble, gint, …) és összetett típust, illetve támogatja ezek felhasználását saját típusok létrehozásakor, illetve a GObject-ből származó osztályokban adattagként való elhelyezését. Emellett megvalósít egy – az objektumok állapotváltozásainak követésére szolgáló – kommunikációs alrendszert (signal). Fentiek létrehozásakor kifejezett cél volt a rugalmas bővíthetőség és a könnyű adaptálhatóság más nyelvekre. Ez utóbbi a később () említésre kerülő nyelvi változatok egyszerű megvalósíthatóságának előfeltétele.

GModule

A GModule egy dinamikus modulok betöltésére szolgáló függvénykönyvtár, mely rendelkezésre áll mindazon rendszereken, ahol a GLib is, elfedve ez egyes operációs rendszer különbözőségeit ezen a területen.

GThread

A GThread célja erősen hasonlatos az imént említett GModule-éhoz, vagyis biztosítani egy, a platformok sajátosságaitól független megoldást ezúttal nem a dinamikus modulbetöltés, hanem a szálkezelés tekintetében.

GIO

Az eddigieket folytatva a GIO is egy, a multiplatformos programozás során gyakran felmerülő probléma – jelesül a fájlok, fájlrendszerek, meghajtók kezelése – megoldására született. A megfelelő POSIX hívásokkal eddig is megvalósítható volt egy kvázi platformfüggetlen fájlkezelés, így ennek önmagában nem lenne számottevő haszna. A GIO ugyanakkor több, mint egy egyszerű POSIX hívásokat burkoló függvényhalmaz. A GObject-re támaszkodva egy magasabb szintű, dokumentumközpontú interfészt valósít meg.

Cairo

A Cairo egy eszközfüggetlen6 kétdimenziós vektorgrafikai függvénykönyvtár, melyet kifejezetten a hardveres gyorsítókkal való együttműködésre terveztek, s mellyel a GDK a kétdimenziós rajzolási feladatait végzi. Érdemes megemlíteni, hogy a Cairo nem a GNOME, hanem a freedesktop.org projekt része.

Pango

A Pango szövegek képi formában történő előállításáért (rendering) és megjelenítésért (lay out) felelős a GTK-n belül, de természetesen a GTK-tól függetlenül is használható, lévén a függvénykönyvtár az előbbiekhez hasonlóan számos platformot támogat.

Nyelvi változatok

GTK minus minus

A gtkmm, illetve annak függőségei adják a GTK projekt C++ nyelvű változatát. Ezek a függvénykönyvtárak wrapperek az eredeti C változat fölött, az ebből fakadó előnyökkel és korlátokkal együtt. Ezen kódok jelentékeny része wrapper mivoltukból következően generált, ugyanakkor számos helyen – ahol ez funkcionalitáshoz a programozási nyelvhez leginkább illeszkedő megvalósításához szükséges – eredeti kódot is tartalmaz.

A C, illetve C++ nyelvű változatok a lehető legkisebb mértékben térnek el egymástól. Ez egyben azt is jelenti, hogy az egyes nyelvi változatok nem tartalmaznak a többihez képest többlet funkcionalitást. Nem lehet azonban eltekinteni az egyes programozási nyelvek adta lehetőségek előnyeitől, hátrányaitól, melyek könnyebbé vagy nehezebbé teszik a GTK adott nyelven való használatát.

Libsigc++

A gtkmm implementációjánál használt függvénykönyvtár, ami lehetővé teszi a szignálkezelés típusbiztos megvalósítását, mely a C változatnál – a nyelvi sajátosságok okán – nem adott.

PyGobject

A Python változat – ahogy számos más egyéb nyelvi variáció is – alapjai gyökeresen megváltoztak a GTK+ új főverziójának megjelenésével. A korábbi – a gtkmm által is alkalmazott – módszer, az eredeti változatot rejti el, burkolja be (wrap), vagy egy köztes réteget képez a C, illetve a cél nyelv – esetünkben a Python – között. Ezen réteg többé-kevésbé természetesen automaták (pl: generátor szkriptek) révén jön létre, ugyanakkor igaz az, hogy nem közvetlenül az eredeti kódbázist használja a burkoló réteg létrehozására. Következésképpen a GTK+ publikus felületében bekövetkező változásokat az egyes wrappereknek rendre követniük kell, holott a GTK+ kódjának írásakor is adottak azok a metaadatok, melyek mondjuk egy Perl, vagy Python elkészítéséhez szükségesek.

GObject Introspection

Az előbbi gondolatot tovább fűzve juthatunk el ahhoz a kézenfekvő kérdéshez, hogy miért nincsenek az említett metaadatok rögtön a GTK+ – illetve a GLib, Pango és a többi függőség – kódja mellett, ahonnan kinyerve azokat az egyes nyelvi változatokat egyszerűen generálni lehetne. A GObject Introspection pontosan ezt célozza. Egy binding elkészítéséhez szükséges adatok a C nyelvű változatok – kódjában egy erre a célra meghatározott formátumban – megjegyzésként szerepelnek, a különböző nyelvű változatok pedig ezt felhasználva jönnek létre.

Összehasonlítás

Az egyes változatoknak megvannak a maguk – jellemzően a programozási nyelv sajátosságaiból következő – előnyei. Ilyenek lehetnek például a C nyelv, illetve a fordítók széles körű elterjedtsége, a C++ azon sajátossága, hogy a nyelv nyújtotta módszereket, mint például az örököltetés, itt közvetlenül használhatjuk ki, vagy a Python nyelvű fejlesztés sebessége. Míg mondjuk a C nyelv esetében bizonyos funkciók kissé nehézkesen használhatóak, addig a C++ objektumorientált megközelítése mellett ugyanez a funkció játszi könnyedséggel elérhető, vagy éppen a Python nyújtotta szkript környezet ad könnyebb, rugalmasabb kezelhetőséget. Az említett három változat tekintetében a főbb ismérveket a 1 táblázat tartalmazza.


GTK+gtkmmPyGObject
Nyelv:CC++Python
Implementáció módja:natívwrapperbinding
Objektumorinentált
technikák használata:közvetettnatívnatív
Licenc:LGPLLGPLLGPL
Ismertebb projektek:Evolution, Firefox, Gimp, …GParted, Inkscape, …gedit
Table 1: A GTK+, gtkmm, PyGObject összehasonlítása


Kapcsolódó projektek

Automata tesztelés

A grafikus felületek kapcsán sajnálatosan elhanyagolt terület az automata tesztelés, azon nyilvánvaló tény ellenére is, hogy a felhasználó épp ezeken a felületeken keresztül éri el az érdemi funkcionalitást és nyeri első benyomásait a szoftverrel kapcsolatban, így ennek megjelenése, valamint helyes működése döntő az alkalmazás későbbi sikerességének tekintetében. Ezzel együtt igaz továbbá, hogy teljes körű (end-to-end) megvalósított tesztelés mindenképpen a felhasználó felületről indított akcióval kell induljon és az ugyanott tapasztalt reakció ellenőrzésével kell végződjön.

A GTK felhasználásával fejlesztett felületek tesztelésénél rendelkezésünkre áll a megfelelő keretrendszer, mely lehetőséget teremt, hogy az elkészült felületi elemek működését automaták segítségével teszteljük. A későbbiek során bemutatásra kerülő mintapéldák esetén mindenütt kitérünk majd az azok kapcsán felmerülő tesztelési feladatok megoldásának mikéntjére. Most azonban lássuk nagy vonalakban hogyan is működik ez a tesztelési keretrendszer.

Accessibility Tool Kit

Elsőre talán egymástól távoli területnek tűnik a szoftverek akadálymentesítése (accessibility), valamint a felhasználó felületek automata tesztelése, egy valami mégis összeköti őket. Ez pedig az a követelmény, aminek a szoftver mindkét cél elérése érdekében eleget kell tegyen, ami nem más, mint az alkalmazás vezérelhetősége bizonyos felhasználói interakciók kizárása mellett is. Az automata tesztelés esetén ez gyakorlatilag az összes eszköz (billentyűzet, egér, …) kizárását jelenti, hiszen a felhasználót ez esetben teljes egészében a tesztelést végző szoftver helyettesíti.

A szoftverek akadálymentesítésének biztosítása egy speciális megközelítést igényel, mely a fogyatékossággal élő emberek szoftverekkel végzett munkájának megkönnyítését helyezi előtérbe. Ehhez az ATK csupán annyit tesz, hogy definiál egy interfészt, melyen keresztül az adott szoftvert el lehet érni. Indulva onnan, hogy egy adott alkalmazást ki lehet választani az összes aktuálisan futó alkalmazás közül, folytatva azzal, hogy le lehet kérdezni az általa megnyitott ablakokat, az abban lévő felületi elemeket (widget), egészen odáig, hogy az általuk tárolt értékeket (egy beviteli mező szövege, folyamatindikátor értéke, …), illetve állapotokat (rádiógomb kiválasztott állapota, beviteli mező szerkeszthetősége, …) írni olvasni lehet, rajtuk akciókat (gomb lenyomása, menüelem kiválasztása) végezhetünk.

Gail

Lévén az ATK lényegében csak egy interfész definíció, ahhoz minden esetben7 tartozik egy implementáció, mely az adott felületfejlesztői rendszer működését megfelelteti az ATK által definiáltaknak. A GTK esetén ez az implementáció a Gail.

Dogtail

A Dogtail egy Python nyelven írt és Python nyelven használható tesztautomatizációs eszköz, illetve keretrendszer. Segítségével létrehozhatók felhasználói felületek – a már említett ATK interfészen keresztül – tesztelő szkriptek, többféle formában és módon is. Ami a módot illeti, lehetőségünk van egyrészről effektíve forráskód – azaz egy Python szkript – formájában létrehozni a tesztjeinket, vagy úgymond ”felvételt” készíteni magáról a tesztelésről, majd az így rögzített eseményeket mint tesztet visszajátszani. Ha az előbbi módszernél maradunk – amiről a további részekben is szó esik –, akkor is két lehetőség adódik, hiszen a Dogtail rendelkezik egy procedurális, illetve egy objektumorientált megközelítésű API-val, melyek tetszés szerint használhatóak a tesztek elkészítésekor.

Accerciser

Mind az automata tesztelő szkriptek megírásakor, mind egy konkrét alkalmazás felületének feltérképezésére, mind pedig az ATK interfésszel történő ismerkedésre alkalmas eszköz az Accerciser, mely az akadálymentesített szoftverek feltérképezésére szolgáló eszköz és mint ilyen pontosan azon adatok megjelenítésére és módosítására, valamint azon akciók végrehajtására alkalmas, amire a Dogtail szkriptek révén képesek vagyunk.


1
a GTK rövidítés alatt a továbbiakban az általánosságban vett grafikus felhasználói felület fejlesztői eszközt, míg GTK+ alatt ennek C nyelvű változatát értjük
2
a 3.0.0 verzió megjelenése időpontja 2011. február
3
a Linux alapú rendszerek, a GDK születésének idejében, gyakorlatilag kizárólagos grafikus szervere
4
ezen szolgáltatáshoz eléréséhez websocket támogatásra van szükség a böngészőben, illetve a Broadway elnevezésű backend bekapcsolására a GTK+ fordításakor
5
példának okáért a Compiz, a Midnight Commander, vagy a syslog-ng
6
értsd hardvereszközöktől független
7
már amennyiben az adott grafikus felületfejlesztői rendszer – mint amilyen a GTK+, vagy mondjuk a Qt – biztosítani kívánja az ATK-n keresztüli elérést

 


A Helló Window! cikksorozat könyv formába szerkesztve a Scribd online könyvtárban érhető el.
A mű létrejöttét a FSF.hu Alapítvány a Szabad Szoftver Pályázat keretében támogatta.

[.] More

Tisztán vagy szabadon?

kedd, augusztus 28, 2012 @ 10:08 DE. Author: Pfeiffer Szilárd

 

A nyílt forrású szoftverek, illetve a nyílt szabványok közszférában való meghonosítása kapcsán az elmúlt években tett erőfeszítések eredményei jól mutatják; a feladat központi akarat, szabályozás és támogatás nélkül sem lehetetlen, egy jól kidolgozott és kellően kommunikált informatikai stratégia mentén nyilván lényegesen egyszerűbb. Bár a kormányzat alig néhány hónapja határozott döntéseket hozott – melyben elkötelezi magát a nyitás mellett –, most mégis olybá tűnik, nem eszik olyan forrón a nyílt forrás kásáját.

A közigazgatási szervek által használt elektronikus dokumentumok formátumáról és a nyílt forráskódú irodai szoftverek használatáról szóló kormányrendelet kimondja, hogy ez év első negyedévének végére az intézmények közötti kommunikációban kizárólag nyílt szabványon alapuló dokumentumok használhatóak, míg az ügyfelek tekintetében alkalmasnak kell lenni ezek befogadására, illetve előállítására. Zárt forráskódú irodai szoftvereket csak műszakilag vagy gazdaságilag indokolt esetben szerezhetőek be. Némi bizonytalanságot kelt azonban, hogy a Tisztaszoftver program mely a köz- és felsőoktatásban résztevők számára volt hivatott jogtiszta szoftvereket beszerezni – előbb megszűnni látszott, majd néhány nappal az ez irányú bejelentést követően mégis meghosszabbításra került.

A cikk a ComputerWorld-Számítástechnika 2011/7-8. számában jelent meg és a Creative Commons “Nevezd meg! – Így add tovább!” licenc feltételei mellett terjeszthető.

Ahogy azt Szakál Péter – a nyílt forrású megoldásokat kínáló Open SKM Agency marketingvezetője – elmondta, az oktatást és a közigazgatást ebben a tekintetben élesen el kell választani egymástól, lévén az eltérő környezetből fakadóan eltérőek a követelmények és az azokra adandó válaszok is. Már a köz- és a felsőoktatás sem vonható egy kalap alá ebben a tekintetben, hiszen míg az alap-, illetve a középfokú intézményekben általános célú képzés folyik, közel azonos feltételek mellett és célok elérése érdekében, erős központi irányítás mellett, addig a felsőoktatási intézmények komoly önállóságot élvezve specializált tudást kell átadjanak.

A közoktatási intézmények követelményeinek ma már gyakorlatilag bármely nyílt forrású operációs rendszer képes megfelelni. Az olyan alapfunkciók, mint a böngészés, levelezés, irodai, illetve csoportmunka feladatok ellátására számos szoftver létezik, melyek jelentékeny részét a felhasználók már amúgy is ismerik, lévén azok (Mozilla Firefox, LibreOffice, …) nemcsak, hogy a különböző Windows rendszerek alatt elérhetőek, de esetenként piaci részesedésben meg is előzik a zárt forrású versenytársaikat. Szakál Péter megítélése szerint ezen a területen a változásoknak érdemi akadálya nincs, sőt a közoktatás homogenitása adta előnyt is könnyen ki lehet használni. Ami hiányzik, az egyrészről az a központi támogatás, mely oktatás, terméktámogatás révén segíti az átállási folyamatot, illetve a központi kontroll mely a folyamat végrehajtását ellenőrzi. Másrészről pedig egy szemléletváltás, mely a termékek oktatása helyett a módszerek ismertetés helyezi előtérbe, jelesül a Microsoft Officevagy éppen LibreOfficehelyett a szövegszerkesztést oktat, olyan tudás birtokába juttatva a tanulókat, mely terméktől függetlenül értékes.

A felsőoktatási intézmények helyzete természetesen merőben eltérő a fentiektől, hiszen a specializált, magas színvonalú szaktudás átadása a feladatuk, ahol elengedhetetlen azon operációs rendszerek használata, melyeken az oktatott szoftverek futnak. Ugyanakkor viszont a nyílt forrású rendszereknek komoly hagyományai vannak a kutatási területeken, ahogy a felsőoktatásban is, különösen tekintettel a természettudomány szakokra, de természetesen azokon kívül is. Az alkalmazói szoftverek a felsőoktatásban komoly reklámértéket képviselnek. Különösen ha kiemelt – akár több millió forint értékű – orvosi, kereskedelmi vagy mérnököknek készült rendszerekről van szó (AutoCAD, ArchiCAD, Photoshop, …) – hiszen az egyetemről kilépők jó eséllyel lesznek hosszú távú ügyfelei az adott szoftvergyártónak. Joggal elvárható tehát, hogy a reklámértékért cserébe a szoftvergyártója vállalja a használattal összefüggő költségeket, a szakalkalmazás mellett a futtatáshoz szükséges operációs rendszer költségeit is. Erre a megoldásra már jelenleg is található példa, és éppen a milliárdos központi finanszírozás teszi értelmetlenné, hogy az intézmények maguk tárgyaljanak a szakalkalmazások gyártóival.

Maga a Tisztaszoftver program példázza talán leginkább az állami szoftverbeszerzések körüli problémákat. Korábban az állam, a köz- és a felsőoktatásban részvevőknek – hallgatói és oktatói oldalon egyaránt – közpénzből finanszírozta a Microsoft szoftverek licenceit. Csaknem egy éve – hat hónappal az után, hogy a kormány meghirdette a digitális megújulás cselekvési tervet – a milliárdos nagyságrendű kiadásokkal járó program valamelyest karcsúsodott, a kormány megszüntette a hallgatói licenceket finanszírozását. Tavaly év végén a program honlapja egy kurta bejelentésben tájékoztattat az érintetteket, hogy az NFM szakmai álláspontja szerint a köz- és felsőoktatási intézmények igényeit – néhány speciális esettől eltekintve – képesek kielégíteni az ingyenesen használható nyílt forráskódú szoftverek is, így a program ez év első negyedévével kivezetésre kerül. Ez a bejelentés egészen új helyzetet teremtett mind a szabad szoftverek, mind az oktatási intézmények, mind állam szempontjából. A nyílt forrás képviselői úgy láthatták kezdetét veszi Magyarországon az évek óta halogatott szoftverpiaci liberalizáció, megteremtődik a valódi piaci verseny lehetősége. Az oktatási intézmények részéről merültek fel aggályok a gyors kivezetés kapcsán, hiszen egy tanév közbeni átállás nem feltétlenül szerencsés és az érettségizők időben való tájékoztatása a vizsgán használható szoftverekről sem betartható kötelezettség ilyen feltételek mellett. A hevenyészett kommunikáció viszont nem feledtetheti, hogy a közoktatási intézményeknek már korábban is lett volna módjuk az átállásra, vagy annak előkészítésére, persze ennek semmiképp sem katalizátora egy olyan rendszer, mely nem ösztönzi a költséghatékony megoldások keresésére a szervezeteket. Hozzá kell tenni, hogy a hardverekkel együtt vásárol licencek (OEM) használatára továbbra is jogosultak az intézmények, vagyis akár két lépcsőben is megvalósulhat a váltás, ahol első körben az alkalmazói szoftverek (levelezés, irodai szoftverek, …) cseréje valósulhat meg, a jelenlegi operációs rendszerek cseréje pedig a számítógép park folyamatos megújítása során kerülhetnek sor. Az állam még azzal együtt is pénz takaríthat meg, hogy utóbb a Tisztaszoftver program ez év végéig történő meghosszabbítása mellett döntött, mivel jobb alkupozícióból folytathat tárgyalásokat például Microsoft szoftverlicencek beszerzéséről. Kérdés persze, hogy az intézmények ezek után komolyan veszik-e kötelezettségeiket, vagy továbbra is abban bíznak, hogy a központi költségvetés jövőre ismét „megmenti” őket az átállással járó feladatoktól.

Nagyrészt a forráshiány okozza az intézményi felhasználók nagy részénél az áttérés megfontolását. Azonban nem szabad tévhitekbe ringatnunk magunkat, nagyon nem mindegy, hogy milyen szoftvereket választunk és milyen szakértelemmel látunk neki a feladatnak. – nyilatkozta Szentiványi Gábor, az ULX Nyílt Forráskódú Tanácsadó és Disztribúciós Kft. ügyvezetője. Mi kizárólag vállalati szintű megoldásokban gondolkodunk, melyekhez akár tíz évig is biztosított egy adott verzió frissítése és karbantartása. Nem mindegy természetesen, hogy néhány tíz, több száz, esetleg több ezer gépből álló rendszerekről beszélünk. Nagy megtakarítás nagy méretű rendszereknél jelentkezik igazán, ebből nem lehet kiemelni csak a szervert, vagy csak a desktopokat. Komplex megoldásokban kell gondolkodni, melyek a teljes életciklust lefedik és megkönnyítik a rendszer üzemeltetőinek munkáját. Persze az ilyen rendszerek bevezetése és támogatása nem ingyenes, azonban meglepően költséghatékony és rugalmas végeredménnyel szolgálnak, versenykörnyezetükkel összehasonlítva, tette hozzá Szentiványi Gábor.

A nyílt forrás, illetve a nyílt szabványok megjelenésének haszna, ahogy az oktatási területeken sem, a közigazgatásban sem elsősorban anyagi természetű, még ha nyilvánvalóan ebben is megmutatkozik. A racionalizálás, vagyis az igények és a folyamatok felmérése és azt követően az ezeknek megfelelő eszközök megtalálása a valódi előny, hiszen ez vezethet el az érték alapú versenyhez. Konkrét példával élve a Microsoft Office, illetve a LibreOffice kölcsönösen rendelkeznek olyan funkcióval mellyel a versenytárs nem. Ugyanakkor egy olyan környezetben, ahol az intézmények közötti kommunikáció jelentékeny részben papír alapú, a számítógépet, mint „okos” írógépet használják, lényegében csak az alapfunkciók számítanak. Nem lehet eléggé hangsúlyozni, hogy a szoftverekhez kapcsolódó szolgáltatások – mint amilyen az oktatás, támogatás – elengedhetetlenek. Ezek költségeinek jelentékeny része az állam számára bevételt jelenhet, amennyiben a feladattal olyan helyi vállalkozásokat bíz meg, mely helyben adózik, magyar állampolgárokat foglalkoztat, megbízásait hazai cégeknek adja. A nyílt forrás közvetlen előnyei mellett segíthet felszámolni azon zárt forrású szoftverek használatát, melyek évtizedes platformokhoz kötődnek, vagy egy-egy funkció megvalósítására (pl.: nyomtatás) kifejezetten pazarló módon kötik magukat tulajdonosi szoftverekhez. Az erőforrások ilyen mérvű pazarlása természetesen megengedhetetlen, különösen, hogy a honi közigazgatás példának okáért messze lemaradt a versenyszférában megszokott internetes ügyintézési lehetőségektől.

A gazdasági kényszer szülte átállás forintokban megragadható előnye egyelőre nehezen konkretizálható. Ivacs Gabriella, a Nyílt Dokumentum Formátum Szövetség elnöke szerint, egy több éves átmeneti időszak dől majd el a küzdelem. Migrációs tapasztalatok hiányában is tudjuk, hogy a támogatás, az oktatás, az adatkonvertálás hosszútávú költségei számottevő kiadásokkal járnak, a hibrid megoldások pedig könnyen a visszarendeződéshez vezethetnek, ezért törekedni kell a lehető leggyorsabb és legteljesebb átállásra, mind az operációs rendszerek, mind programok szintjén. A “best practices” kommunikálása, a felhasználók informálás fontos feladat, mivel szemléletbeli fordulatra is szükség van, főleg az informatika oktatásban. Különbséget kell tenni azonban szabványos formátumok és nyílt forrású rendszerek között, a kettő összemosása kormányrendelet egyik hibája, holott a cél elsődlegesen az interoperabilitás megteremtése, a szabványos és hosszútávon is megőrizhető, olvasható digitális adatvagyon kialakítása.

Az állami szféra nyílt szabványos alapokon történő átszervezése gyártófüggetlen megoldások keresztül hozzájárulhatnak a valós piaci verseny kialakulásához, a szükséges és elégséges megoldások megtalálásához, az illegális szoftverhasználat visszaszorításához. Ugyanakkor a program sikere nem elsősorban a törvényeken, hanem végrehajtás mikéntjén áll, vagy bukik. A gyakorlat fogja bizonyítani, hogy az olyan rugalmas megfogalmazások, mint a nem nyílt forrású irodai szoftverek csak műszakilag vagy gazdaságilag indokolt esetben szerezhetőek be, mit is jelentenek. A teljes váltás nem szükséges és nem is lehetséges, az viszont kérdés, meddig és mennyi idő alatt lehet eljutni. A jelenlegi és jövőbeni informatikai közművek fejlesztéseknél figyelemmel lesznek-e például a platformfüggetlenség kérdésére. Sikerül-e kialakítani a megfelelő pályázati, oktatási, támogatási és szankciórendszereket, hogy a központi akarat hatékonyan válhasson valósággá. A lehetőség adott, a cél látható és elérhető – tisztán és szabadon.

A cikk a ComputerWorld-Számítástechnika 2012/09-10. számában jelent meg és a Creative Commons “Nevezd meg! – Így add tovább!” licenc feltételei mellett terjeszthető.

[.] More

Mire jó a Zorp?

szerda, február 8, 2012 @ 09:02 DE. Author: Pfeiffer Szilárd

Ha egy marketing anyag sorai közé tévedt volna a nyájas olvasó, a fent megfogalmazott költő kérdésre a válasz bizonnyal az lenne; mindenre. Így viszont némi szerénységet felmutatva szűkítjük ezt a halmazt belátva, hogy a Zorp közel sem mindennek a teteje, viszont minden funkcióban segítségünkre lehet, ami a mély protokollelemző proxy tűzfal varázsos meghatározás alapján elvérható. Lássuk mik lennének ezek.

Hozzáférés-vezérélés

A proxy szerverek ezen alapvető funkciója esetén a Zorp annyiban különleges, hogy itt is el tud szakadni az alacsonyabb ISO/OSI rétegektől. Míg más eszközöknél csak a hálózati réteg attribútumai –IP címek, illetve alhálózatok– segítségével szabályozhatjuk a szolgáltatásokhoz való hozzáférést, addig a Zorp egy saját rendező mechanizmusa alapján. Ez a zóna, ami alhálózatok szabadon csoportosítható halmazát jelenti, melyek fába szervezhetőek. A zónastruktúra szintje között a szolgáltatások elérésének szabályai öröklődnek, de kivételek is megadhatóak, így egy IP alhálózatoktól független adminisztratív hierarchia hozható lére, ami nem a hálózati eszközeink elrendezését, hanem a hozzáférés szabályait tükrözi.

A kik mellett a hozzáférés-vezérlési rendszer megalkotásakor a mit és a hogyan kérdéseire is választ kell adnunk, vagyis nem csak az határozandó meg, hogy kik számára érhetőek el az erőforrások, hanem azt is, hogy milyen feltételek mellett. Lehet szó egész egyszerűen csak arról, hogy egy szerver elérése esetén minden egyes végrehajtott műveletről bejegyzés készül a rendszernaplóba. Vagy tilthatjuk például a szerver, illetve a kliens olyan funkcióit, melyek inkompatibilitást okoznak. Szűrhetjük a protokoll némely elemeit, módosíthatjuk azok értékeit, hogy elkerüljük érzékeny adatok kiszivárgását. Ellenőrizhetjük az adattartalom vírusmentes mivoltát. Kibonthatjuk, majd visszacsomagolhatjuk a forgalmat védő titkosítást. Ebből adnak ízelítőt a következő fejezetek.

Adatszivárgás megelőzése

Számos protokoll esetén léteznek olyan –egyébiránt teljesen szabályos, következésképp a tűzfalak által alapvetően nem szűrt, vagy tiltott– protokollelemek, melyek a kliensen futó szoftverekről, esetleg annak hálózati beállításairól szivárogtatnak ki adott körülmények között érzékeny információkat. Erre példa a user-agent mező –egyebek mellett– a HTTP protokoll fejlécében, ahol is ennek értéke a használt böngésző típusa, illetve verziója, mely akaratunkon kívül jut el az általunk éppen meglátogatott webszerverhez.

Ehhez közel azonos módon szolgáltathatja ki a böngésző a proxy beállításokat, gépünk IP címét, vagy épp az előzőleg látogatott oldal (amiről az adott szerverre jutottunk) URL-jét. Ehhez hasonló megoldások azonban nem csak a HTTP protokollban szerepelnek, hanem másutt is, melyekről érdemes tudni és adott esetben érdemes őket tiltani. Erre a Zorp könnyen használható és rugalmas megoldást ad.

Interoperabilitás

A fenti példánál maradva a böngésző típusának nem csak az elrejtése lehetséges, de a ”meghamisítása” is, ami az interoperabilitás megteremtéséhez annyiban segít minket hozzá, amennyiben találkozunk azzal az –egyébiránt egyre ritkábban előforduló– esettel, amikor is egy webszerver annak ellenére is kikötést tesz a böngésző típusára, hogy erre érdemben alátámasztható oka nincs. Az ilyen helyzet könnyedén feloldható azáltal, hogy a böngésző által küldött user-agent mező értékét úgy változtatjuk meg, hogy az a webkiszolgáló számára elfogadható legyen.

Éppúgy kellemetlenségeket okozhat bizonyos –általában nem mai keletű– szoftvereknél a titkosított forgalom kezelésének hiánya, főként ha egy nem megbízható hálózaton keresztük kell átjuttatnunk az adatokat. Erre a helyzetre természetesen számos megoldás létezik, ugyanakkor ha problémát megfejeljük azzal, hogy a forgalmat egy tűzfalon is szeretnénk keresztül vezetni, akkor válik igazán hasznossá a Zorp azon szolgáltatása, mely alkalmassá teszi a kliens és a szerver irányába más titkosítási metódus (TLS, SSL) használatára. Ennek szélsőséges esete, amikor a Zorp csak az egyik –megbízhatatlannak tartott– irányba végez titkosítást, míg a másik –megbízhatónak vélt– irányba nem.

Fentiekhez nem feltétlenül elegendő csupán a titkosított csatornák kiépítésének, valamint újraépítésének képessége, lévén a protokoll részeként is kezdeményezhető titkosított kapcsolat kiépítése, ami az interoperabilitás egy újabb területére, a funkciók elrejtésére vezet át minket. Amennyiben azt szeretnénk, hogy bizonyos –mind a kliens mind a szerver által ismert– funkciók ne legyenek elérhetőek –mondjuk valamilyen inkompatibilitási probléma miatt– ezt is módunkban áll a Zorp segítségével megtenni.

A titkosítási példát tovább gördítve adott a lehetőség, hogy az SMTP protokoll feature listájából, kiemeljük a STARTTLS elemet, ami megakadályozza, hogy a kliens és a szerver ilyen módon kommunikáljanak egymással. Az SSL beállítások egyes kombinációinál –például ha a szerver irányába kötelező az SSL– azt a Zorp automatikusan megteszi.

Tartalomszűrés

Nincsen tűzfal tartalomszűrés nélkül. A szabály alól a Zorp sem kivétel, még akkor sem, ha a Zorp önmagában korlátozottan alkalmas is ezen feladat ellátására. A hangsúly az önmagában kifejezésen van, hiszen kiegészítve egyéb eszközökkel, mint vírus-, spam-, URL szűrő, a Zorp, mint suszter, maradhat a kaptafánál, vagyis nem tesz egyebet, mint értelmezi az adott protokollt, ennek segítségével kiemeli a hálózati forgalomból azt a részt (URL, letöltendő fájl, levél, …), melyet továbbadva a alkalmas szoftvernek (pl: ClamAV, SpamAssassin, …), annak visszajelzése alapján döntést hozhat, hogy visszautasítja, átengedi, karanténba helyezi, vagy csak naplózza az eseményt függően akár más beállításoktól, vagy a forgalom egyéb körülményeitől. Ehhez nem kell egyebet tennünk, mint egy illesztőprogramot helyezni tűzfalunk és az általunk választott tartalomszűrést biztosító eszköz közé, mely tudatja előbbivel az utóbbi által megállapítottak alapján hozott döntést.

Audit

Egy hálózat adminisztrációja során az erőforrások elérését szabályozó rendszer felállítása csupán az első lépés, ennek a rendszernek a felügyelete adja a munka nagyobb részét, melyből a szabályok fejlesztése, átalakítása következik majd. Mindenekelőtt azt kell megtudnunk, mi az ami a hálózatunkban aktuálisan zajlik. Egyrészről mik azok az események, amik a megalkotott szabályrendszer áthágására irányultak. Másrészről az engedélyezett akciók közül melyek következtek be és milyen formában. A Zorp mindkét típushoz tartozó eseményekről bejegyzéseket ír a rendszernaplóba.

Az utóbbi eset az, ahol az applikációs szintű protokollelemzésnek hasznát láthatjuk, hiszen az egyes protokollok parancsainak szintjén van módunk rálátást szerezni a hálózatban zajló eseményekre, illetve rögzíteni azokat a rendszernaplóba. Ennek mind belső, mind külső audit során komoly hasznát vehetjük, hiszen –a naplózás megfelelő beállításai mellett– igazolható, hogy mely események történtek meg és melyek nem egy adott időszakban. Emellett természetesen forgalmi-, illetve eseménystatisztikák alapjául szolgálhatnak ezek a naplóbejegyzések.

Flexibilitás

Az eddig leírtak kész, vagy legalábbis félkész megoldásként a Zorp részét képezik, ugyanakkor a Zorp erejét épp az adja, hogy szabadon és rugalmasan bővíthető saját céljaink szerint. Ehhez a Zorp azon sajátosságát használhatjuk ki, hogy a már meglévő eszközöket (proxyk) könnyen újrahasznosíthatjuk, vagyis a az egyes protokollelemzőket nem kell újra megírnunk, elegendő azok Python nyelvű változatait újra felhasználva csak a szükséges kiegészítéseket megtenni. Még abban az esetben is igaz ez, ha teljes egészében magunk kívánjuk a hálózati forgalmat feldolgozni –amit egyébiránt megtehetünk úgy is, hogy hozzá megírjuk a alkalmas C nyelvű proxyt–, mivel létezik egy erre a célra szolgáló Python osztály (AnyPy). Minden, ami az OSI modell alsóbb rétegeiben zajlik, implementált a proxyban, nekünk csak az ezen felüli –leginkább az alkalmazási– réteg részleteire kell koncentrálnunk.

A Zorp kipróbálásához, teszteléséhez “kulcsrakész” virtuális gépek itt érhetőek el.

A hivatalos Zorp GPL oldal itt található.

A blog bejegyzés, illetve a kapcsolódó videó a Creative Commons “Nevezd meg! – Így add tovább!” licenc feltételei mellett terjeszthető.

[.] More

Mi az a Zorp?

kedd, január 24, 2012 @ 02:01 DU. Author: Pfeiffer Szilárd

Tömören fogalmazva a Zorp egy nyílt forrású mély protokollelemző proxy tűzfal, ami roppant tudományosan hangzik. Vegyük sorra a mágikus kifejezés kulcsszavait és próbáljuk meg értelmezni őket!

Protokoll elemzés

Funkciójukból fakadóan a tűzfal alkalmazás mindegyike képes a hálózati forgalom bizonyos mérvű elemzésére, lévén ennek hiányában nem tudnánk a forgalom szabályozására vonatkozó feltételeket megadni. Ez a Zorp esetén sincs másképp. A különbség az egyes szoftverek között az elemzés mélységében mutatkozik meg. Míg például az Netfilter esetén az oly sokat emlegetett ISO/OSI modell negyedik – azaz szállítási – rétegén túli elemeket nemigen használhatjuk fel a hálózati forgalom szabályozásánál, addig a Zorp esetén akár a legfelső – azaz az alkalmazási – réteget górcső alá véve is hozhatunk döntéseket. A döntés vonatkozhat a szolgáltatás egészére, vagyis tilthatjuk, vagy engedélyezhetjük például egy FTP szerver teljes elérését a felhasználó egy csoportja számára, de lehet szó arról is, hogy letölteni engedünk, feltölteni viszont már nem és a előbbit is csak akkor, hogy ha a letöltendők vírusmentesnek bizonyultak.

Proxy tűzfal

Csaknem minden ami ezen kifejezésről eszünkbe juthat, igaz a Zorp kapcsán is. Elsőként mindenképpen a proxy szerverek azon sajátossága, hogy beékelődnek a kliens és a szerver közé elszeparálva ezáltal a hálózati kommunikáció két szereplőjét egymástól és minden egyes kérést, illetve választ újrafogalmaznak a két szereplő között. A Zorp ebben a tekintetben annyival több versenytársainál, hogy mindezt applikáció szinten tudja megtenni, a felhasználás különböző módjai (forward proxy, reverse proxy) mellett. Ehhez szükségesek a C nyelven implementált, viszont Python nyelven konfigurálható, bővíthető protokollelemző osztályok (a Zorp terminológiájában proxy), melyekből a nyílt forrású változat esetén kilenc, míg a kereskedelmi verzióban huszonöt áll rendelkezésre.

Modularitás szerkezet

A Zorp kétség kívül legnagyobb előnye a testreszabhatóság, ami a szoftver szerkezetének moduláris felépítése nélkül nem volna lehetséges. A mindennapi használat során ez annyit jelent, hogy ha nincs szükségünk egy adott proxy kapcsán speciális működésre, akkor gyakorlatilag szinte semmit nem kell tennünk azért, hogy élvezhessük a hálózati protokoll applikációs szintig történő elemzését. Ha viszont ennél többre van igényünk, kontrollálni kívánjuk az adott forgalmat, akkor is csak arra kell összpontosítanunk, amit konkrétan elérni szeretnénk (pl: egy HTTP header módosítása), minden egyebet készen kapunk. Ezeken túl viszont arra is van lehetőség, hogy a Zorp csak a hálózati kapcsolatot kezelje és a protokoll teljes elemzését magunk végezzük függetlenül a gyárilag meglévő proxyktól.

A szállítási rétegben megvalósított biztonsági protokollok (SSL/TLS) implementációja egy önálló alrendszert, ha úgy tetszik modult képez, így az ezekhez kapcsolódó beállítások a konkrét applikációs protokolltól függetlenül tehetőek meg, ami lehetőséget biztosít arra is, hogy egy általunk implementált protokollértelmező SSL kapcsolaton belül és kívül egyaránt működhessen. A Zorp alkalmas külső eszközökhöz, modulokhoz való integrációra is, melynek lévén viszonylag könnyen valósíthatóak meg spam-, illetve vírusszűrő megoldások.

Nyílt forrás

A Zorp egy alapvetően nyílt forrású termék, ám néhány kérdést mégis érdemes tisztázni, ha más nem, a licenchuszárok kedvéért. Egyrészről, hogy a szerzői jogi védelem pontosan milyen formájáról van szó, másrészről, hogy kizárólagos-e ez a licencelés. Az első kérdésre kettős válasz adható, lévén a Zorp is két összetevőre oszlik, magára a tűzfal applikációra, illetve egy hozzá kapcsolódó függvénykönyvtárra. Mindkettő az FSF által kiadott licenc – ez előbbi (Zorp) GPL, míg az utóbbi (libzorpll) LGPL – hatálya alatt érhető el. A második kérdésre adott válasz egy kifejezésben foglalható össze; dual-license, vagyis létezik egy nyílt forrású (Zorp/Zorp GPL), illetve egy kereskedelmi (Zorp Professional) változat, a megfelelő licencelési, illetve számos technikai különbséggel, melyekről később még esik szó.

A Zorp kipróbálásához, teszteléséhez “kulcsrakész” virtuális gépek itt érhetőek el.

A hivatalos Zorp GPL oldal itt található.

A blog bejegyzés, illetve a kapcsolódó videó a Creative Commons “Nevezd meg! – Így add tovább!” licenc feltételei mellett terjeszthető.

[.] More

Zorp, a pokoli operátor tűzfala

kedd, november 15, 2011 @ 04:11 DU. Author: Pfeiffer Szilárd

A november 12-én Budapesten, illetve a szeptember 13-án Szegeden megrendezett szabad szoftver konferenciákon tartott előadás célja a Zorp protokollelemezési és -módosítási képességében rejlő számos lehetőség közül néhány olyan felvillantása volt, mely a mindennapi rendszergazdai gyakorlatban előforduló problémákra ad megoldásokat.

Ilyenek lehetnek a vírusszűrés különböző (HTTP, SMTP) protokollokban, az egyes részelemek szűrése, módosítása, tiltása (pl: referer host, user-agent headerek értékének cseréje HTTP forgalom, csak read-only parancsok engedélyezése FTP protokoll esetén), SSL titkosítás kibontása, szerver audit lehetőségek.

Az előadások prezentációja itt, a benne található demó videók itt érhetőek el.

[.] More

Zorp Professional 3.4

csütörtök, május 12, 2011 @ 08:05 DE. Author: Pfeiffer Szilárd

Megjelent a Zorp Professional 3-as sorozatának új stabil ága 3.4 néven, mely számos újítást tartalmaz, mind az operációs rendszer, mind tűzfal, mind az egyéb szoftverek tekintetében.

Ezek közül is elsőként említendő, hogy a korábbi Zorp OS verzió -ami még az Ubuntu Dapper Drake-re épült- most átadta helyét az operációs rendszer 4.0-ás változatának, melynek alapjául szintén Ubuntu szolgált, konkrétabban a legutolsó LTS változat, vagyis a Lucid Lynx. Ennek megfelelően az egyes csomagok mellett a kernel is frissült méghozzá a 2.6.32-es verzióra, mely szélesebb körű hardvertámogatás mellett performanciajavulást is jelent.

Az új operációs rendszer egyszersmind új telepítőt is jelent, ahol két módozat közül választhatunk. Az egyszerűsített (simple) változat egy közel automatikus telepítést tesz lehetővé, ahol az felhasználónak csak a legszükségesebb információkat szükséges megadnia, míg a másik (expert) úton haladva a telepítés részleteiben testre szabható, ezzel együtt viszont számottevően több kérdésekkel fogunk szembesülni.

Az operációs rendszer  frissítése kapcsán érdemes megjegyezni, hogy a Zorp OS 4.0-ás verziója a korábbi i386/amd64 páros helyett már csak amd64 architektúrára érhető el. A korábbi i386-os installációkat tehát újra kell telepíteni, míg 64 bites változatok enélkül is frissíthetőek.

A Zorp kapcsán a legfontosabb  változások között szerepel egyrészről, hogy az SMTP proxy az új verzióban már támogatja a STARTTLS parancsot, másrészről, hogy az SSL proxy pedig biztonsági javításokon (SSL re-negotiation,  TLS plain text injection) esett át. Ezeken túl egy új proxy is bekerült a már meglévők közé (XMLSec), melynek  révén XML adatforgalom -egyebek mellett SOAP kérések- validálását teszi lehetővé.

A menedzsment rendszer (ZMS) újdonsági közül kiemelendő, hogy a OpenVPN kapcsolatok megadása számos új opcióval, valamint a felhasználói felület adta rugalmasságát növelő elemmel gazdagodott. A tartalomszűrés szolgáltatás (ZCV) részeként a támogatott vírusellenőrző szoftverek (eset, virus buster) is frissítésre kerültek.

[.] More