Gyermekkel nem rendelkező objektumok ....................................................... 34
4.4.
Egy egyszerű mini-alkalmazás ......................................................................... 35
4.5.
A meta-adatfájl ................................................................................................. 35
4.6.
A mini-alkalmazás fájlstruktúrája .................................................................... 37
4.7.
Újítások a frontend részben .............................................................................. 37
4.7.1. Dokkolás támogatása ....................................................................................... 38 4.7.2. A Graph osztály................................................................................................ 41 4.7.3. A Chart osztály................................................................................................. 42 4.7.4. Animáció a mini-alkalmazásban ...................................................................... 44 4.7.5. Magonkénti CPU monitorozás ......................................................................... 46 4
4.7.6. Selector ablak és az egyedi beállítások ............................................................ 48 4.7.6.1. A Settings osztály ......................................................................................... 49 4.7.6.2. A Props osztály ............................................................................................. 50 4.7.6.3. A Selector osztály ......................................................................................... 51 4.7.7. Hardverenkénti monitorozás ............................................................................ 54 4.7.8. Teleptöltöttség mérés ....................................................................................... 55 4.7.9. Vezeték nélküli hálózatok ................................................................................ 57 5.
ELŐSZÓ Manapság mindennapi életünk részévé vált a számítógép. Munka közben épp úgy, mint otthon kikapcsolódáskor, gyakorlatilag napi aktivitásunk jelentős részét számítógép előtt végezzük. Ám akár az üzleti, akár a szórakoztató szempontot nézzük, nem mindegy számítógépünk sebessége. Már mindenki találkozott azzal a helyzettel, hogy az addig villámgyors számítógépe egyszer csak minden különösebb ok nélkül elkezdett lassulni. Ilyenkor az ember tétlenül áll, és próbál a feladatkezelőbe vagy épp valamilyen egyéb teljesítményfigyelő eszközbe eljutni, hogy megtudja, mi is okozza a galibát. Mennyivel szebb volna, ha az asztalon, egy helyen, valósidőben láthatnánk a rendszerünk egyes erőforrásainak kihasználtságát. Erőforrás alatt jelen dolgozatban a processzorokat, memóriát, hálózati csatolókat, logikai lemezmeghajtókat, valamint hordozható számítógépek esetén a telepeket értem. A processzor és memória kihasználtság mérésének okát, gondolom, senkinek sem kell bemutatnom. A hálózati csatolók aktivitásának (küldött és fogadott bájtok két mérés között) mérése hasznos lehet például hálózatbiztonság szempontjából: Ha a rendszer indokolatlanul nagy hálózati forgalmat generál, akkor az egy esetleges rosszindulatú program vagy behatoló jelenlétére utalhat. Logikai lemezmeghajtók aktivitásának (írott és olvasott bájtok két mérés között) mérése szintén kifizetődő lehet, ha meg szeretnénk tudni, hogy mely meghajtó dolgozik épp a legnagyobb aktivitással a rendszerben, stb. Egy teljes egészében bináris állományú program és a széles körben való elterjesztéshez szükséges weboldal készítése helyett egy olyan megoldást kerestem, ahol az elkészült programot könnyen publikálni tudom, és egyszerűen kaphatok visszajelzéseket a felhasználóktól. Ezért döntöttem a Yahoo mini-alkalmazások mellett. Azon felül, hogy egy nagyméretű galéria áll rendelkezésre, ahonnan a felhasználók kedvükre választhatnak a mini-alkalmazások közül, könnyen és egyszerűen írhatnak azon visszajelzéseket, észrevételeket is a fejlesztőknek. További érdekessége és kihívása a projektnek, hogy megvalósításához három különböző nyelvet kellett használni: A mini-alkalmazás felületének és működésének leírására az XML és JavaScript nyelveket, míg a script nyelven nem megvalósítható funkciókhoz (Windows API hívások) Delphi környezetet használtam.
6
1.
BEVEZETÉS
A projekt megkezdése előtt igyekeztem felmérni, hogy a témában már milyen minialkalmazások születtek, azok mennyire voltak sikeresek. Ekkor döntöttem úgy, hogy egy már meglévő és bizonyított widget-et fogok továbbfejleszteni a felhasználói visszajelzések alapján. Így esett a választásom a SimpleMonitor nevű minialkalmazásra, melyet VIKTORAS AGEJEVAS készített még 2005-ben. A widget alapvetően megvalósította a CPU (igaz az összes magra összegezve) és memória kihasználtság monitorozását, valamint a hálózati csatolók aktivitás mérését. Ezen felül megtudhattuk még a külső IP címünket, valamint a rendszer elindulása óta eltelt időt is. A mini-alkalmazás publikálása óta eltelt időben szép számban érkeztek felhasználói visszajelzések, így tudtam, hogy a widget mely részei nyerték el a felhasználók tetszését, valamint mely részein lenne érdemes javítani, vagy épp új funkciókat hozzárakni. Az eredeti mini-alkalmazást mutatja be az alábbi ábra (1. ábra):
1. ábra. Az eredeti mini-alkalmazás A visszajelzések böngészése közben (melyek közül néhány, a fejlesztés szempontjából is érdekeset a függelékben is feltüntettem) megtudtam, hogy a felhasználók leginkább a magokra lebontott CPU monitorozást, és a testreszabhatóságot (mely grafikonok jelenjenek meg és melyek ne) hiányolják. Ezen felül akadt pár idegesítő hiba is, ilyen volt például, hogy a widget csak rendszergazdai jogosultságok mellett volt telepíthető, vagy épp az, hogy eltávolítása esetén felesleges bejegyzések maradtak a rendszerleíró adatbázisban. Miután írásos formában is engedélyt kaptam a szerzőtől, hogy módosítsam saját elképzelésem szerint a kódot, már nekiláthattam megtervezni az új funkciókat, melyek a következők: • •
Több magra lebontott processzor-kihasználtság mérés Lemez-teljesítmény monitorozása 7
• • • • • •
Eszközönkénti monitorozás (a felhasználó kiválaszthatja, mely lemez vagy hálózati interfész aktivitása legyen megjelenítve) Dokkolás támogatása Testreszabhatóság (a felhasználó kiválaszthatja, mely grafikonok jelenjenek meg a mini-alkalmazás felületén) Animáció támogatása (egy-egy grafikon megjelenítése, vagy elrejtése animáltan történik) Teleptöltöttség mérése WiFi monitor
Természetesen az esetlegesesen felfedezett hibákat is igyekeztem javítani. A fentieket megvalósítva az alábbi ábra (2. ábra) mutatja a felújított mini-alkalmazást:
2. ábra. A felújított mini-alkalmazás Az újításoknak még része lett volna egy, a folyamatok monitorozására alkalmas újabb mini-alkalmazás is, ám mivel ez újabb bináris állományok létrehozását jelentette volna, így az a jelen verzióból kihagyásra került. Ennek oka, hogy a Yahoo csapata nem részesíti előnyben az olyan widget-eket, melyekben nagy mennyiségű bináris állomány található, mivel azok esetleges kártékonysága nehezen ellenőrizhető (a script fájloknál 8
ez nyílván egyszerűbben megoldható). Jelen sorok írásakor a felújított mini-alkalmazás már elküldésre került a Yahoo csapatához (ellenőrzésre), azonban a galériába ekkor még nem lett felvéve, így sajnos linket egyelőre nem tudtam mellékelni. A dolgozat a mini-alkalmazás telepítésének és használatának rövid bemutatásával kezdődik, majd ezek után először a back-end majd a front-end rész kerül górcső alá. Feltételezem, hogy az olvasó már valamilyen szinten tisztában van a COM alapfogalmaival, valamint a Delphi COM támogatásának lehetőségeivel, így a back-end rész bemutatásakor ezekre külön nem fogok kitérni. A front-end rész tárgyalásakor viszont nem feltételezhető, hogy az olvasó már foglalkozott Yahoo minialkalmazásokkal, így ott először a widget-ek készítésének alapfogalmait és mikéntjét mutatom be a legfontosabb szempontok figyelembevételével, majd ezek után kerülnek tárgyalásra az egyes függvények, valamint osztályok. Összességében a dolgozat szerkesztése folyamán igyekeztem azon elemeket kiemelni, amelyek újításnak számítanak jelen verzióban.
9
2.
TELEPÍTÉS
Mint ahogy az már az előszóban is megemlítésre került, a mini-alkalmazások nem lefordított programok, hanem XML és JavaScript nyelvekkel leírt, futásidőben értelmezett szoftverek. Ezen értelmezéshez a Yahoo a Widget Engine-t biztosítja, melyhez a motoron kívül számos mini-alkalmazást is mellékelnek. Jelen projekt futtatásához először telepítenünk kell a 4.0-ás (vagy újabb) verziójú motort, melyet a http://widgets.yahoo.com/download/ oldalon érhetünk el (a telepítőkészlet a CD mellékleten is megtalálható). A telepítés után regisztrálásra kerülnek a .kon és .widget kiterjesztésű fájlok, így már futtathatjuk a widget-et. Harmadik féltől származó widget-ek nem rendelkeznek digitális aláírással, így mielőtt a motor megnyitna egy ilyen fájlt, egy figyelmeztető ablakkal tudatja, hogy nem feltétlen biztonságos a végrehajtani kívánt művelet. Itt válasszuk ki, hogy ennek ellenére használni szeretnénk a mini-alkalmazást (többször ezt a kérdést nem fogja feltenni a motor, míg ugyanazt a verziójú widget-et használjuk). Érdemes megjegyezni, hogy a WMI használata miatt az első indulás Windows Vista vagy Windows 7 rendszerek esetén eltarthat pár másodpercig. A főablak megjelenését követően a monitorozás máris megkezdődik (minden beállítás még az alapértelmezett). A beállítások testreszabásához a főablakra kattintsunk a jobb egérgombbal. Ekkor az előugró menüből válasszuk a „Widget Preferences” menüpontot, ahol beállíthatjuk a mini-alkalmazás kinézetét és a grafikonok működését.
3. ábra. A mini-alkalmazás beállítások ablaka Látható, hogy a fenti ábra (3. ábra) épp az egyik újítást, a testreszabhatóságot mutatja. Kiválaszthatjuk, mely grafikonok legyenek megjelenítve a főablakon. A beállítások ablak többi opciója nagyjából megegyezik az eredeti mini-alkalmazás beállítások ablakával, azzal a különbséggel, hogy a beállítások csoportokra lettek bontva (Colors, Appearance, Font, stb.). A másik ablaktípus (és újítás), melyet érdemes megjegyezni, a szintén jobb klikkel elérhető eszközválasztó ablak. 10
Ezt az ablakot a „Select Network Interface” és a „Select Disk Drive” menüpontok alatt érhetjük el, és az alábbi ábra (4. ábra) mutatja.
4. ábra. Az eszközválasztó ablak Kezelését tekintve a többi opció szerintem egyértelmű, és nagyban hasonlít az eredeti alkalmazás kezeléséhez, így azt most nem magyarázom el részletesen. Egy widget eltávolításához elegendő bezárnunk azt, majd a Dokumentumok mappa „My Widgets” almappájából egyszerűen töröljük az eltávolítani kívánt minialkalmazást. Érdemes megjegyezni, hogy a „Documents and Settingas/user_name/Local Settings/Yahho/Widget Engine/” útvonal alatt található „Unzipped/widget_name” és „Widget Data/widget_name” mappákban egyéb fájlok is lehetnek, melyekre a minialkalmazás futtatásakor volt szükség.
11
3.
BACK-END RÉSZ
A projektben a monitorozáshoz szükséges információkat a mini-alkalmazás back-end része biztosítja. A back-end rész nem más, mint egy COM kiszolgáló, mely egy OLE objektumot bocsát a kliens rendelkezésére. A mini-alkalmazás front-end része JavaScript-en alapul, ilyen módon Windows API-kat nem lehet direktben elérni belőle. Egy lehetséges megoldás a problémára OLE objektumok használata, melyre a Widget Engine 2.0-s motorjától van lehetőség. A back-end rész fejlesztése Delphi fejlesztőkörnyezetben1 történt, tesztelésére egy külön alkalmazást készítettem (TestSM), melyet a függelékben részletesebben is bemutatok.
3.1. Kiszolgáló regisztrációja Hogy egy COM kiszolgáló által közzétett osztályokat külsőleg is példányosítani lehessen, a kiszolgáló első futása előtt regisztrálnunk kell a kiszolgálót és osztályait. A regisztráció során közzétett információk tárolása a Windows rendszerleíró adatbázisában történik. Alapértelmezésben ezen információk elérési útvonalai és leírásai a teljesség igénye nélkül a következők: •
•
•
Computer\HKEY_CLASSES_ROOT\library.coclass: Az osztályazonosítót tartalmazza. Így például a SimpleMonitor.Information karakterlánc (ProgID) könnyen a keresett osztály GUID-jává konvertálható. Computer\HKEY_CLASSES_ROOT\CLSID\classid: A kiszolgáló elérési útvonalát, szálmodelljét, ProgID-ját, típus könyvtár azonosítóját, valamint verzió információkat tartalmazza. Computer\HKEY_CLASSES_ROOT\TypeLib\libid: Adott típuskönyvtár verziónkénti információját tartalmazza.
Látható, hogy ezen információk mindegyike a HKEY_CLASSES_ROOT gyökér alatt található, melynek írását a rendszer csak rendszergazdai jogosultságok mellett teszi lehetővé. És itt ütközünk problémába: Mivel a widget tulajdonképpen egy zip fájl, azt a motornak az első futást megelőzően ki kell, hogy csomagolja. Ez tipikusan a „Users\user\AppData\Local\Yahoo\WidgetEngine\Unzipped\Widget Name.widget\” mappa alá történik. Mivel a mini-alkalmazás COM kiszolgálója is ezen zip fájlban található, ezért azt az első futás előtt regisztrálni kell. A probléma egyfelől, hogy nem feltétlenül rendelkezik a felhasználó rendszergazdai jogosultságokkal (emiatt például Vista alatt az eredeti widget már képtelen volt elindulni), továbbá, hogy sikeres regisztráció esetén egy másik felhasználónak is el kellene tudnia érni az előző felhasználó által regisztrált elérési útvonalat.
1
A háttéralkalmazásban használt módszerek bemutatása során (tipikusan forráskódok), egyes típusok és függvények nincsenek a Delphi futásidejű könyvtárában deklarálva. E felhasznált típusok és függvények mindegyike megtalálható a JEDI API könyvtárban, melyet a http://jedi-apilib.sourceforge.net/ címen érhetünk el.
12
Ezen problémákra két megoldás létezik: •
•
Az első, hogy az első futás előtt egy telepítő például a Program Files könyvtárba telepíti és regisztrálja a kiszolgálót (melyet minden felhasználó „lát” és innentől feleslegessé válik az újbóli regisztráció). Ám ennek hátulütője, hogy mindenképpen legalább egyszer rendelkeznie kell a telepítő felhasználónak rendszergazdai jogosultságokkal, valamint a mini-alkalmazás törlése esetén a kiszolgálót manuálisan kell eltávolítani a gépről. A második, és sokkal szebb megoldás az lenne, ha minden felhasználó számára regisztrálhatnánk egy saját kiszolgálót. Windows 2000-től felfelé erre már lehetőség van [2], igaz nem igen foglalkozik vele még az MSDN sem. A „Computer\HKEY_CLASSES_ROOT\” aktuális felhasználóbeli megfelelője a „Computer\HKEY_CURRENT_USER\Software\Classes\”. Mint ismeretes, a HKEY_CURRENT_USER gyökér alá tartozó kulcsok már rendszergazdai jogosultságok nélkül is módosíthatók, így máris lehetővé válik a felhasználónkénti regisztráció.
Itt érdemes megjegyezni, hogy a mini-alkalmazás bezárásakor törölhetjük is az előző regisztrációt, mellyel megelőzzük, hogy felesleges bejegyzések maradjanak a regisztrációs adatbázisban a widget törlése után (az eredeti mini-alkalmazás ezt szintén nem tette meg). Mivel a kiszolgáló végzi saját maga regisztrációját (dll esetén az alkalmazás telepítője, vagy regsvr32.exe hívja meg a DllRegisterServer vagy DllUnregisterServer függvényeket, míg exe esetén indítási paraméter lehet a /regserver vagy a /unregserver), így azt a programozó valósítja meg. Következésképpen a regisztrációs algoritmust a fentiek tükrében kellett módosítani.
3.2. Az Information osztály A kiszolgáló egyetlen „közzétett” (rendelkezik osztálygyárral, így a CoCreateInstanceal külsőleg példányosítható) osztálya, az Information osztály (5. ábra). Az osztály megvalósítja az IDispatch interfészt, így script nyelvekből is használható (nem kell ismernünk a metódusainak és tulajdonságainak nevét, valamint paramétereit fordításidőben). Az osztály egy példányának fő feladata a különböző monitorozni kívánt modulokat (processzorok, interfészek, lemezek, stb.) reprezentáló belső objektumok létrehozása és közzététele.
13
5. ábra. Az Information osztály Látható, hogy a mini-alkalmazás által megvalósítandó (új) funkcióknak megfelelőek a kiszolgáló egyes tulajdonságai.
3.3. Belsőleg példányosított osztályok Ezen osztályok szintén megvalósítják az IDispatch interfészt, ám ezt a TAutoIntfObject osztállyal [3] teszik. Az osztály leszármazottainak példányosítása csak belsőleg lehetséges (a konstruktorukkal), mivel nem rendelkeznek osztálygyárral. [3] Ezt leszámítva az ilyen módon létrehozott objektumok ugyanúgy használhatók az OLE automatizmusban, mint az osztálygyárral rendelkező társaik. Az osztály példányosításakor annak konstruktorában kézileg kell betölteni az osztályhoz tartozó típuskönyvtárat. Ehhez a LoadTypeLibrary függvényt [3] lehet használni, melynek bemenő paramétere a modul neve, melyhez a típuskönyvtár linkelve lett. Modul nevének megszerzéséhez a ParamStr(0) [15] (csak exe-k esetén) vagy GetModuleName(HInstance) [15] (exe és dll-ek esetén egyaránt) függvényeket használhatjuk. Ezt követően már hívható az örökölt konstruktor [3], melynek két paramétere van: • A típuskönyvtárat reprezentáló interfész (ITypeLib [4]) • A létrehozni kívánt objektum GUID-ja. Ezt mutatja be a következő egyszerű kód: type TMyExternal = class(TAutoIntfObject, IMyExternal, IDispatch) protected {IMyExternal methods} public constructor Create; ... end; ... constructor TMyExternal.Create; begin inherited Create(LoadTypeLibrary(GetModuleName(HInstance)), IMyExternal); //other initialization end;
1. forrás. Egy belsőleg példányosítható osztály váza
14
A megoldás előnye, hogy elég csak az előzőekben már bemutatott Infomation osztályt regisztrálni a COM kiszolgáló telepítésekor. Az összes többi osztályt pedig az Information fogja példányosítani (belsőleg), így azoknak csak a típuskönyvtárát kell a kiszolgálóhoz csatolni.
3.4. ISimpleList Az Information osztály lista-tulajdonságait definiáló interfész. Az interfész az IDispatch leszármazottja (hogy egy-egy lista tulajdonság is használható legyen az OLE automatizmusban), így az azt megvalósító osztályt érdemes a TAutoIntfObject-ből származtatni (3.3. fejezet). Az interfész két tulajdonsággal rendelkezik, a Count-al és az Item-el. Előbbi a listában megtalálható elemek számát határozza meg, míg utóbbi egy tömbtulajdonság, mely a listában szereplő egyes elemekhez enged hozzáférést. Az Item tulajdonság egyes elemei OleVariant típusúak. Ennek előnye, hogy a lista tetszőleges elemeket tartalmazhat, egyszerű típusúakat (karakterlánc, egész, stb.) épp úgy, mint OLE objektumokat (az IDispatch interfészt megvalósító osztályok példányait, a Delphi automatikusan OleVariant-tá tudja alakítani). Az alap elképzelés az, hogy az Information típusú objektum egy lista tulajdonságának (processzorok, interfészek, lemezmeghajtók) olvasásakor példányosítunk az adott tulajdonsághoz tartozó ISimpleList-et (6. ábra) megvalósító osztályt, mely listában az egyes elemek (szintén OLE objektumok) az egyes processzorokat, interfészeket, stb. reprezentálják.
6. ábra. Az ISimpleList interfész A későbbiekben, mikor egy-egy lista tulajdonságot tárgyalok, nem kerül bemutatásra a teljes osztály, ehelyett egy példaprogramot láthatunk majd, melynek segítségével már könnyedén megtervezhető egy ISimpleList-et megvalósító osztály az adott tulajdonsághoz (a cél az adott tulajdonságokhoz szükséges információk megszerzésének bemutatása, nem pedig egy-egy osztályimplementáció leírása).
3.5. CPU A mini-alkalmazás egy új funkciója a magonkénti CPU használat monitorozása. Mivel a Widget Engine nem támogatja a magonkénti monitorozást, ezért azt a kiszolgálónak kell megvalósítania. Megjegyzendő, hogy az eredeti widget a processzor nevét és sebességét is mutatta, ezért célszerű lesz ez utóbbit is magokra lebontva megjelenítenünk. Nézzük most ezt a két feladatot külön-külön.
15
3.5.1. WMI A WMI a Windows operációs rendszerek beépített szolgáltatása, amely lehetővé teszi a gépek távoli felügyeletét és menedzselését hálózaton keresztül. Ehhez a WMI interfészeket használ, melyeket gyakorlatilag az összes COM/OLE programozást támogató környezetből (akár script nyelvekből is) elérhetünk. A Microsoft a WMI segítségével valósítja meg a Web alapú vállalatirányítási rendszert (WBEM). A WMI alapszintű használatához (lekérdezésekhez) az alábbi négy interfészt érdemes ismerni: IWbemLocator interfész [5] Az interfész egyetlen feladata, adott névtérhez egy IWbemServices interfész biztosítása. Egy kliens, mely Windows irányítási szolgáltatásokat igényel, elsőként a CoCreateInstance-al (Delphi alatt CreateCOMObject függvénnyel) létrehoz egy lokátort. E lokátorral érjük el a távoli (vagy helyi) számítógépen magát a Windows Management-et. Ehhez az interfész a ConnectServer metódust biztosítja, mely létrehozza a kapcsolatot a WMI névtérrel a meghatározott számítógépen. Esetünkben ez a helyi számítógép CIMV22 névtere lesz („Root\CIMV2”). Helyi számítógépről lévén szó, a kapcsolódáskor sem hosztot, sem felhasználónevet, sem jelszót nem kell megadni. Sose hívjuk a ConnectServer-t a DllMain()-en belül. Windows Vista és Windows 7 rendszerek esetén ez az alkalmazás kifagyását okozhatja (ez a hiba nincs hivatalosan dokumentálva, ám elszórva3 találkozhatunk a hiba leírásával az interneten). • IWbemServices interfész [5] A kliens által használt interfész, melynek feladata a WMI szolgáltatásokhoz való hozzáférés biztosítása. Metódusai közül az ExecQuery- t érdemes megemlíteni, mely egy WQL4 lekérdezést futtat adatok megszerzéséhez. A lekérdezés formailag a következőképpen néz ki: „SELECT tulajdonság1, tulajdonság2,…,tulajdonságN FROM osztálynév a névtérből WHERE tulajdonság = valami”5. Amennyiben az osztályhoz létezik olyan példány a rendszerben, melyre a WHERE feltétel igaz, úgy a metódus sikeresen lefut. Megjegyzendő, hogy ha adott példány összes tulajdonságát meg szeretnénk tudni, akkor a tulajdonságok felsorolása helyett használhatunk egyszerűen „*”-ot is. Továbbá mivel a WHERE feltétel is csak opcionális, így azt is elhagyhatjuk. Mivel egy osztálynak több példánya is lehet (például Win32_Processor osztálynak kétmagos rendszerek esetén két példánya lesz), a megadott feltételektől függően (a feltételek használata csak opcionális vagy több
•
2
Az általános információs modell (CIM) általános információkat szolgáltat a rendszerről és a hálózatokról. 3 http://social.msdn.microsoft.com/Forums/en-US/windowscompatibility/thread/92221542-22f1-487aa6ae-9e9f69999405 4 WMI Query Language (SQL szintaktika) 5 Bővebb információt CIMV2 névtérhez tartozó osztályok listájáról a WMI referencia oldalain találunk
16
példányra is igaz lehet), ezért az egyes példányok tulajdonságait egy felsorolásban kapjuk vissza. • IEnumWbemClassObject interfész [5] A gyakorlatban az előbb említett ExecQuery sikeres lefutása esetén biztosítja számunkra ezt az interfészt, mely a fentebb említett felsorolást (0 vagy 1 elemű is lehet) tartalmazza. A listán az interfész Next metódusával haladhatunk végig. Ha elértük a lista végét, a metódus visszatérése WBEM_S_FALSE lesz. • IWbemClassObject interfész [5] Az IEnumWbemClassObject interfész Next metódusa (sikeres lefutás esetén) bocsátja rendelkezésünkre. Az interfész elsődleges feladata az egyes osztályokhoz tartozó példányok tulajdonságainak olvasása és manipulálása. Jelen esetben csak tulajdonságok olvasására lesz szükségünk, melyhez a Get metódust használhatjuk. A metódus egy WideString paraméterben, szövegesen várja a tulajdonság nevét, melynek értékére kíváncsiak vagyunk (a tulajdonság névnek szerepelnie kellett a WQL lekérdezésben). A tulajdonság értékét egy Variant típusú változóban kapjuk meg (az egyes tulajdonságok típusai különbözőek lehetnek). 3.5.2. Magonkénti CPU aktivitás Most, hogy a WMI lekérdezésekkel alapszinten már tisztában vagyunk, nézzük miként is lehet kinyerni az aktivitást: Először is a magok/processzorok számát kell meghatározni. Ezt számos módon megtehetjük, használhatjuk például a GetSystemInfo függvényt. [6] Ezek után szükségünk van még a Win32_PerfRawData_PerfOS_Processor osztály példányaira is, melyek nyers adatokat (a RawData utal erre) tartalmaznak az egyes processzorok/magok aktivitásáról. Az osztály Name tulajdonsága határozza meg, hogy az osztály egy példánya hányadik maghoz/processzorhoz tartozik. A felsorolás 0-tól kezdődik és a processzor/magszám mínusz 1-ig tart. Aktivitásszámoláshoz az egyes példányok PercentProcessorTime és Timestamp_Sys100NS tulajdonságaira lesz szükségünk, melyek Int64 típusúak. Ezek ismeretében a lekérdezés a következőképp alakul: „SELECT PercentProcessorTime, Timestamp_Sys100NS FROM Win32_PerfRawData_PerfOS_Processor WHERE Name = keresett mag indexe”. Az aktivitásszámolás pedig így: 1) Delta = (old) Timestamp_Sys100NS - Timestamp_Sys100NS 2) DeltaProc = (old) PercentProcessorTime – PercentProcessorTime 3) Activity= 100.0 – (DeltaProc / Delta) · 100 Látható, hogy a lekérdezést legalább kétszer kell lefuttatni, hogy értelmezhető adatokhoz jussunk. Most lássunk egy példát az előzőek demonstrálására:
17
const Flags = WBEM_FLAG_FORWARD_ONLY or WBEM_FLAG_RETURN_IMMEDIATELY; var Locator: IWbemLocator; Svc: IWbemServices; Enum: IEnumWbemClassObject; Instance: IWbemClassObject; Returned: Cardinal; Value: OleVariant; begin Locator := CreateComObject(CLSID_WbemLocator) as IWbemLocator; OleCheck(Locator.ConnectServer(’Root\CIMV2’, ’’, ’’, ’’, 0, ’’, nil, Svc)); OleCheck(CoSetProxyBlanket( Svc, RPC_C_AUTHN_WINNT, 0, nil, RPC_C_AUTHN_LEVEL_CALL, 3, nil, 0 )); OleCheck(Svc.ExecQuery( ’W QL’, ’SELECT * FROM Win32_PerfRawData_PerfOS_Processor’, Flags, nil, Enum )); while Enum.Next(W BEM_INFINITE, 1, Instance, Returned) = WBEM_S_OK do begin OleCheck(Instance.Get(’PercentProcessorTime’, 0, Value, nil, nil)); W riteLn(String(Value)); end; end;
2. forrás. A WMI használata 3.5.3. Processzor név és sebesség A két tulajdonság megszerzéséhez a regisztrációs adatbázist kell használni. Processzor információkra a „Computer\HKEY_LOCAL_MACHINE\HARDWARE\DESCRIPTION\ System\CentralProcessor\ProcessorIndex” útvonal alatt tehetünk szert, ahol a ProcessorIndex az előzőekhez hasonlóan 0-tól processzor/magszám mínusz 1-ig tart. Mivel csak olvasni szeretnénk a HKEY_LOCAL_MACHINE gyökér alól, így azt korlátozott jogosultságú felhasználó is megteheti. A fentebb említett kulcs alatt a „ProcessorNameString” és „~Mhz” bejegyzéseket kell olvasni. Az első egy karakterlánc, míg a második egy hosszú egész típusú bejegyzés.
18
var s: String; Registry: TRegistry; begin s := ’ HKEY_LOCAL_MACHINE\HARDWARE\DESCRIPTION\System\CentralProcessor\0’; Registry := TRegistry.Create; try Registry.RootKey := HKEY_CURRENT_MACHINE; if Registry.OpenKeyReadOnly(s) then begin WriteLn(Registry.ReadyString(’ProcessorNameString’)); WriteLn(Registry.ReadInteger(’~Mhz’)); end; finally Registry.Free; end; end;
3. forrás. Processzorinformációk kinyerése a regisztrációs adatbázisból 3.5.4. IProcessorInformation interfész A fentiek ismeretében már meg lehetett tervezni saját osztályomat is (7. ábra). A tervezés során érdemes volt kihasználni, hogy mind a WMI lekérdezésnél, mind a regisztrációs adatbázisnál egy fix karakterlánc állt a rendelkezésre („SELECT…WHERE name = Index” és „\Computer\...CentralProcessor\Index”), ahol a különböző példányoknál csak az index különbözik. Így a gyakorlatban egy listát hoztam létre, mely megvalósítja az ISimpleList interfészt. A lista elemszámát a GetSystemInformation-ból tudtam meg, míg a lista egyes elemeit az indexük alapján hoztam létre (a megfelelő karakterláncokba a megfelelő indexeket helyettesítettem), majd végrehajtottam a megfelelő lekérdezéseket (WMI és registry). A lista egyes elemei megvalósítják az IProcessorInformation interfészt.
7. ábra. Processzorinformációk publikálására használt osztályok
19
3.6. Hálózati interfészek A mini-alkalmazás egyik újítása az interfészenkénti aktivitás-mérés. Mielőtt ennek mikéntjébe mélyebben is beleásnánk magunkat, érdemes először az alapfogalmakkal tisztában lennünk. Windows rendszerekben egy hálózati interfész egy IP szintű absztrakció, míg egy adapter egy adatkötés szintű absztrakció. E két absztrakció között egy az egy szerinti megfeleltetés van. A gyakorlatban ez azt jelenti, hogy egy kapcsolat tulajdonságait (küldött, fogadott csomagok, kapcsolat állapota, stb.) az interfész, míg ehhez a kapcsolathoz tartozó hardver tulajdonságait (fizikai cím, hálózati cím, alapértelmezett átjáró, stb.) az adapter reprezentálja. Egy interfészt és a hozzá tartozó adaptert is ugyanaz az index azonosítja, valamint nevük és leírásuk is azonos (ez az egy az egy szerinti megfeleltetésből következik). A két absztrakcióhoz különböző függvények tartoznak, ám ezek mindegyike az iphlpapi (IP Helper) könyvtárban található. [7] Jelen esetben a hálózati aktivitást szerettem volna mérni, így az interfész-kezelő függvények közül az alábbi kettőt kellett csak használnom. Lássuk most ezeket kicsit bővebben: •
GetInterfaceInfo függvény [7] Az első függvényünk a GetInterfaceInfo, mellyel az aktív hálózati adapterek (nincs letiltva az eszközkezelőben), és az ezekhez tartozó interfészek indexeit tartalmazó listát szerezhetjük meg. A függvény hívása előtt érdemes tudni, hogy az nem foglalja le a lista számára a helyet, lefoglalásáról (valamint később a hely felszabadításáról) nekünk kell gondoskodni, továbbá a lista csak az IPv4 protokollt használó adapterek indexeit fogja tartalmazni. A függvénynek két paramétere van. - Az első egy kimenő paraméter, mely az indexek listájára mutató pointer (PIpInterfaceInfo). Ezt a paramétert beállíthatjuk nil-nek, amennyiben csak a lista mérete érdekel minket. Ezt tipikusan a függvény első hívásakor szokás alkalmazni, ekkor a méretet a második paraméterben kapjuk majd meg. - A második paraméter egy hosszú előjel nélküli egész, mely ki- és bemenő paraméter is. Bemenő paraméterként akkor használjuk, mikor a listánk számára már lefoglaltuk a helyet a memóriában, és a függvénynek a lista hosszát adjuk át. Amennyiben ez a hossz túl kicsi, e paraméter módosítva lesz, és így megtudhatjuk, hogy mekkora helyet is kell lefoglalnunk a lista számára. Ez utóbbi esetben a függvény visszatérése az ERROR_INSUFFICIENT_BUFFER konstans lesz. Amennyiben nem található engedélyezett adapter, a függvény visszatérése ERROR_NO_DATA. Sikeres lefutás esetén a visszatérés NO_ERROR. 20
•
GetIfEntry függvény [7] A második függvényünk a GetIfEntry, mellyel egy adott indexhez tartozó interfész tulajdonságait tudhatjuk meg. Ezen tulajdonságokat egy rekord (TMIBIfRow) tartalmazza. A függvény egyetlen paraméterrel bír, mely be- és kimenő is egyben. E paraméter a fentebb említett rekordra mutató pointer, mely rekord index mezőjét a GetInterfaceInfo-val megszerzett index lista egy elemére állítjuk. Amennyiben az index mezőt nem állítjuk be, a függvény visszatérése ERROR_INVALID_DATA lesz.
Az alábbi egyszerű példa e két függvény használatát mutatja be. var BufLen: Cardinal; Buffer: PIPInterfaceInfo; I: Integer; IFace: TMIBIfRow ; begin BufLen := 0; if GetInterfaceInfo(nil, BufLen) = ERROR_INSUFFICIENT_BUFFER then begin GetMem(Buffer, BufLen); if GetInterfaceInfo(Buffer, BufLen) = ERROR_SUCCES then for i := 0 to Buffer.NumAdapters – 1 do begin IFace.dw Index := Buffer.Adater[i].Index; if GetIfEntry(IFace) = ERROR_SUCCESS then begin WriteLn(’ Interfész neve:’, String(PChar(@IFace.bPhysAddr))); WriteLn(’Interfész állapota: ’, Integer(IFace.dw OperStatus)); WriteLn(’Küldött bájtok: ’, Integer(IFace.dw OutOctets)); WriteLn(’Fogadott bájtok: ’, Integer(IFace.dw InOctets)); end; end; FreeMem(Buffer); end; end;
4. forrás. Interfész információk megszerzése A fenti egyszerű példából is jól látszik, hogy milyen szerkezetű két COM osztályt volt érdemes létrehoznom: A GetInterfaceInfo függvény segítségével egy lista objektumot szerkesztettem, mely lista elemei a GetIfEntry által megszerzett rekordokból képzett objektumok lettek.
21
8. ábra. Interfészinformációk publikálására használt osztályok A TIPv4InterfaceInfoList osztály az ISimpleList interfészt valósítja meg, míg a TIPv4InterfaceInfo, az IIPv4InterfaceInfo interfészt (8. ábra).
3.7. Lemezmeghajtók Az Information osztály utolsó listatulajdonsága a lemezmeghajtókhoz kapcsolódik. Lemezmeghajtóknak jelen esetben a fizikailag jelen lévő merevlemezeket hívom (ennek oka, hogy csak e lemezek teljesítményszámlálóit lehet megszerezni, erről bővebben a DeviceIOControl függvénynél). Egy lemezmeghajtó teljesítményszámlálóinak lekérdezéséhez szintén két függvényre van szükség. Nézzük most ezeket kicsit bővebben: •
CreateFile függvény [8] A függvény feladata létrehozni vagy megnyitni egy fájlt vagy I/O eszközt. Feladattól függően a függvény paraméterei a következők: - Az első paraméter a megnyitni vagy létrehozni kívánt fájl vagy eszköz neve. Egy eszköz neve a „\\.\” prefixből és a fizikai vagy kötet nevéből6 áll. - A második paraméter határozza meg az eszközhöz vagy fájlhoz igényelt hozzáférési jogosultságokat7. Mivel az eszköz attribútumaihoz kell hozzáférni, ez a paraméter 0. - A harmadik paraméter határozza meg a megosztási módot. Ha egy processz sikeresen megnyitott egy fájlt vagy eszközt, és ehhez a fájlhoz vagy eszközhöz egy másik processz is hozzá szeretne férni (a hozzáférést az előző paraméterben lehet beállítani), akkor azt maximum csak az itt beállított hozzáférési jogosultságokkal teheti meg. Eszköz megnyitásakor a FILE_SHARE_READ és FILE_SHARE_WRITE jelzőket kell használni.
6 7
Fizikai név például a „PhysicalDrive0”, kötet név például a „C:” (lezáró „\” nélkül). Egy fájlt vagy eszközt olvashatunk, írhatunk, lekérdezhetjük az attribútumait, stb.
22
- A negyedik paraméter opcionális és jelen esetben szükségtelen, így az értéke nil. - Az ötödik paraméter határozza meg, az eseményt, mely végrehajtásra kerül, ha a fájl vagy eszköz létezik vagy nem8. Amennyiben egy I/O eszközt szeretnénk megnyitni, ezt a paramétert OPEN_EXISTING konstansra kell állítani. Ebben az esetben a függvény csak akkor fut le sikeresen, ha az első paraméterben megadott eszköz létezik. - A következő két paraméter a fájl attribútumait állítja be. Mivel most nem fájllal operálunk, ez a két paraméter 0. A függvény visszatérése (sikeres hívás esetén) egy érvényes leíró (handle) a fájlra vagy eszközre. Hiba esetén a visszatérés az INVALID_HANDLE_VALUE konstans lesz, mely esetben a GetLastError függvény szolgál bővebb információval a hibáról. •
DeviceIoControl függvény [9] A függvény feladata, hogy adott vezérlőkódot egy meghatározott eszközmeghajtónak (driver) továbbítson. Ez az eszközmeghajtó tipikusan egy kötet, fájl, vagy stream. A függvény paraméterei a következők: - Az első paraméter az eszköz leírója (handle), melyet a például a CreateFile-al lehet megszerezni. - A második paraméter a vezérlőkódja a végrehajtandó operációnak. A vezérlőkódtól függően a függvénynek lehetnek be- és kimenő paraméterei. - A harmadik és negyedik paraméter írja le az opcionális bemenő adatot (tárolóra mutató pointer és a tároló mérete). - Az ötödik és hatodik paraméterben pedig az opcionális kimenő adatot (tárolóra mutató pointer és a tároló mérete). - A hetedik paraméter csak akkor van értelmezve, ha a függvény biztosít kimenő adatot is, ekkor az itt átadott változó a kimenő adat méretét fogja tartalmazni. - A nyolcadik paraméter opcionális, és esetünkben szükségtelen, így ezt nil-re állítjuk. Mivel egy lemez teljesítményszámlálóira (disk performance counter) vagyunk kíváncsiak, ezért az IOCTL_DISK_PERFORMACE operációs kódot kell használni. Ebben az esetben a függvénynek nincs szüksége bemenő adatokra, így a harmadik és negyedik paramétert nil-re illetve 0-ra kell állítani. A kimenő adatok tárolója egy TDiskPerformance típusú rekord lesz, ennek megfelelően az
8
Ha egy fájl létezik, akkor azt felülírhatjuk, vagy megnyithatjuk, ha nem létezik, akkor létrehozhatjuk, stb.
23
ötödik paraméternek egy ilyen típusú változóra kell mutatnia, míg a hatodik értékének meg kell egyeznie a tároló méretével. Megjegyzendő, hogy a függvény nincs értelmezve optikai meghajtók vagy (Windows Vista-nál korábbi rendszereken) tollmeghajtók esetén. Amennyiben ilyen típusú meghajtók teljesítményszámlálóit akarjuk megszerezni, a függvény nem fog sikeresen lefutni és a GetLastError az ERROR_NOT_SUPPORTED konstanssal tér vissza (a fenti problémáról semmilyen információ nincs az MSDN honlapján, így ezt tapasztalati úton tudtam meg). Most, hogy bevezető jelleggel megismertük a két szükséges függvényt, lássunk egy egyszerű példát a használatukra: var hDevice: THandle; PerfData: TDiskPerformance; Returned: Cardinal; begin hDevice := CreateFile( ’\\.\PhysicalDrive0’, 0, FILE_SHARE_READ or FILE_SHARE_WRITE, nil, OPEN_EXISTING, 0, 0 ); if hDevice <> INVALID_HANDLE_VALUE then begin if DeviceIOControl( hDevice, I OCTL_DISK_PERFORMANCE, nil, 0, @PerfData, SizeOf(PerfData), Returned, nil ) then begin WriteLn(Int64(PerfData.BytesRead)); WriteLn(Int64(PerfData.BytesW ritten)); end; CloseHandle(hDevice); end; end;
5. forrás. Lemez teljesítményszámlálóinak megszerzése A COM kiszolgálóban a fizikai lemezek teljesítményszámlálóit egy listán keresztül érhetjük el. A listát az ISimpleList interfész definiálja és a TDiskDrives objektum valósítja meg (9. ábra). A lista minden egyes eleme egy-egy fizikai lemeznek felel meg. A fizikai lemezeket egy kis objektum reprezentálja, mely objektum root tulajdonsága a fizikai meghajtó gyökerét tartalmazza (így egyértelműen azonosítható a lemez), míg a bytesRead és bytesWritten tulajdonságok pedig a DaveiceIoControl függvényben használt tároló azonos nevű mezőinek felelnek meg. A listaobjektum létrehozásakor a kiszolgálónak először a logikai lemezek kötetnevét kell megszereznie. [10] Ehhez használhatjuk a GetLogicalDrives vagy 24
GetLogicalDriveStrins függvényeket. Ezután a GetDriveType függvény segítéségével már meghatározhatjuk, mely lemezekre van értelmezve a fentebb bemutatott operáció9, így már egyszerűen elkészíthető egy lista.
9. ábra. Lemez teljesítményszámlálóinak publikálására használt osztályok
3.8. Vezeték nélküli hálózatok A mini-alkalmazás része a vezeték nélküli kapcsolatok monitorozása is. Ennek megvalósítására kézenfekvő lenne a system.wireless objektumot használni (a system objektumról részletesebben a front-end fejezetben) a widgetben. Ennek az objektumnak a következő tulajdonságai vannak: • • • • • •
available: Wifi telepítve van-e a számítógépen info: összegzés a Wifi státuszáról network: a hálózat neve, mellyel a kapcsolat létrejött noise: zajszint powered: a hálózati interfész be van-e kapcsolva signal: a kapcsolati jel erőssége
Látható, hogy a használt interfész nevét, valamint a kapcsolat biztonsági állapotát (titkosítás engedélyezve van-e) nem tudhatjuk meg, ám ezeket is érdemes a felhasználóval közölnünk. Ezért a widget Information osztálya fel van ruházva WiFi támogatással is, melyhez a Windows Native Wifi API-ját használja. [11] Az API a Wifi teljes körű menedzselését teszi lehetővé a helyi számítógépen, és a Konfigurációmentes Vezeték Nélküli Hálózatok (továbbiakban WZC) szolgáltatást használja10. Következésképp e funkció használatához ennek a szolgáltatásnak engedélyezve kell lennie a helyi számítógépen.
9
Ha a meghajtó típusa DRIVE_FIXED akkor biztosan értelmezve van az operáció. A Windows e funkciója csak a Windows XP harmadik szerviz csomagjától érhető el.
10
25
Lássuk most ezeket a függvényeket kicsit bővebben: •
WlanOpenHandle függvény [11] Feladata a kapcsolat létrehozása a WZC szolgáltatással. A későbbiekben használt függvények legtöbbje paraméterként követeli meg a WlanOpenHandle-vel lekérdezett kezelőt. - A függvény első paramétere egy bemenő paraméter, a kliens verzió, mely meghatározza a legnagyobb verziószámot, amit a kliens (a szoftver) támogat. A Windows XP-vel való kompatibilitás érdekében ezt a paramétert 1-re állítjuk. - A második paraméter egy fenntartott paraméter, melynek értéke kötelezően nil kell, hogy legyen. - A harmadik paraméter egy kimenő paraméter, mely a legnagyobb verzió, melyet a kliens (a szoftver) és a kiszolgáló (WZC) is támogat. A gyakorlatban semmi szükség erre a paraméterre, viszont nem opcionális, így ennek értéke nem lehet nil. - Az utolsó paraméter egy kimenő paraméter, mely a keresett kezelő.
A függvény visszatérései közül egyet érdemes kiemelni (mellyel az MSDN nem foglalkozik), az ERROR_SERVICE_NOT_ACTIVE-t, melyet abban az esetben kapunk, amennyiben a WZC nem fut a helyi számítógépen. A kiszolgálóban így ezt az eshetőséget is érdemes kezelni (ebben az esetben például az available tulajdonság értéke hamis). • WlanCloseHandle függvény [11] Amennyiben az előzőekben megszerzett kezelőre már nincs többé szükségünk, a kezelő lezárásához használjuk a WlanCloseHandle függvényt, mely lezárja a kapcsolatot a WZC szolgáltatással. - A függvény első paramétere a lezárni kívánt kezelő. - A második egy fenntartott paraméter, melynek értéke nil kell, hogy legyen. • WlanFreeMemory függvény [11] Az API használata során a különböző függvények kimenő paraméterei (listák például) számára nem a programozónak kell a helyet lefoglalnia, ezt az adott függvények elvégzik helyettünk. Azonban a memóriát már a programozónak kell felszabadítania, amennyiben az adott paraméterre nincs többé szükség. Ehhez a feladathoz kell használni a WlanFreeMemory-t, melynek egyetlen paramétere a felszabadítani kívánt memóriaterületre mutató pointer. • WlanEnumInterfaces függvény [11] Mint ahogy a neve is mutatja, a függvény feladata az összes engedélyezett WLAN interfész felsorolása. Az eredményt egy listában kapjuk vissza
26
•
(TWlnaInterfaceInfoList), mely tartalmazza az egyes interfészek azonosítóját 11 (GUID), nevét és kapcsolati állapotát. - A függvény első paramétere a bemenő kezelő, melyet a WlanOpenHandle függvénnyel szereztünk meg. - A második egy fenntartott paraméter, melyet mindig nil-re kell állítani. - A harmadik egy kimenő paraméter, a fentiekben már bemutatott lista. A lista számára a függvény foglalja le a memóriát, azonban ha a listára már nincs szükségünk, akkor a lefoglalt memóriát fel kell szabadítanunk. WlanQueryInterface függvény [11] A függvény egy adott interfész különböző paramétereit kérdezi le. A gyakorlatban az interfészt a GUID-jával azonosítja, majd egy operációs kóddal közölhetjük, hogy mely paraméterekre van szükségünk. Ezen paraméterekhez különböző kimenő adatok tartozhatnak, melyhez a függvény elvégzi a szükséges memóriaallokálást. - Az első bemenő paraméter a már jól ismert kezelő. - A második az interfészt azonosító GUID, melyet a WlanEnumInterface által visszaadott listából tudhatunk meg. - A harmadik bemenő paraméter az operációs kód, mellyel megadhatjuk, hogy az interfész mely paramétereire vagyunk kíváncsiak. Az alkalmazásban csak a wlan_intf_opcode_current_connection operációs kódot fogjuk használni, mellyel a kapcsolat állapotát (SSID, jelerősség, biztonság, stb.) leíró rekordot (TWlanConnectionAttributes) kapunk eredményül. Ebben az esetben a függvény csak élő kapcsolat esetén fut le sikeresen. A kapcsolat állapotát is megtudhatjuk az interfészeket felsoroló listából. További operációs kódokat, és azok leírását a Microsoft fejlesztői oldalán találhatunk. [11] - A negyedik paraméter ismét egy fenntartott paraméter. - Az ötödik egy kimenő paraméter, mely az operációs kódhoz tartozó kimenő adat számára lefoglalt memóriaterület méretét adja vissza bájtokban. - A hatodik egy kimenő paraméter, az adat paraméter, mely az operációs kódhoz tartozó eredmény tárolójára (buffer) mutató pointer. A tároló számára a függvény foglalja le a memóriát, és amikor arra már nincs szükségünk, a WlanFreeMemory-vel fel kell azt szabadítanunk. - Az utolsó paraméter egy opcionális paraméter, és nyugodtan nil-re állítható.
11
A későbbiekben az interfész azonosítása annak GUID-ja alapján történik. Az interfészenkénti lekérdezésekhez szükségünk lesz ezen azonosítókra.
27
Lássunk most egy egyszerű példát, hogy a fentebb bemutatott függvények miként is használhatók a gyakorlatban: var hClient: THandle; RetVal, CurVersion: Cardinal; IfList: PWlanInterfaceInfoList; IfInfo: TWlanInterfaceInfo; I: Integer; ConAttr: PWlanConnectionAttributes; BufLen: Cardinal; begin RetVal := WlanOpenHandle(1, nil, @CurVersion, @hClient); if RetVal = ERROR_SUCCESS then begin RetVal := W lanEnumInterfaces(hClient, nil, @IfList); if RetVal = ERROR_SUCCESS then begin for I := 0 to Iflist.dw NumberOfItems – 1 do begin IfInfo := Iflist.InterfaceInfo[I]; WriteLn( ’A(z) ’, IntToStr(Succ(I)), ’. adapter neve: ’, String(IfInfo.strInterfaceDescription) ); if IfInfo.IsState = w lan_interface_state_connected then begin WriteLn(’Az adapter kapcsolódik vezeték nélküli hálózathoz!’ ); RetVal := WlanQueryInterface( hClient, IfInfo.InterfaceGuid, wlan_intf_opcode_current_connection, nil, @BufLen, @ConAttr, nil ); if RetVal = ERROR_SUCCESS then begin WriteLn( ’A jel erőssége: ’, ConAttr.w lanAssociationAttributes.w lanSignalQuality ); WriteLn( ’A kapcsoalt biztosnásgos? ’, ConAttr.w lanSecurityAttributes.bSecurityEnabled ); WlanFreeMemory(ConAttr); end; end; end; WlanFreeMemory(IfList); end; W lanCloseHandle(nil, hClient); end; end;
6. forrás. Vezeték nélküli hálózat információk megszerzése
28
A fentiek tükrében a kiszolgáló tervezésekor három szintet különböztettem meg (10. ábra) • Az első a hardver és szolgáltatás szint, melyet a IWirelessInfo interfész definiál. Kettő, csak olvasható tulajdonsága van. - Az első az available, mely értéke igaz, ha a helyi számítógépen található engedélyezett WLAN interfész, továbbá a WZC szolgáltatás is elérhető. - A második tulajdonság az interface, mely a második szintre, a hálózati interfész szintre vezet. Elméletileg elképzelhető, hogy a helyi számítógépen egynél több WLAN interfész is található, ezért a tulajdonság olvasásakor bejárjuk a fentiekben már kitárgyalt listát, és ha létezik kapcsolódott interfész, akkor azt fogjuk lekérdezni. Ha az available tulajdonság értéke hamis, akkor az interface tulajdonság értéke null variant lesz. •
A hálózati interfész szintet a IWlanInterfaceInfo interfész definiálja. Tulajdonságai: - a kapcsolat állapot (connected), - az interfész név (description) - és végül az utolsó szintre átvezető connection 12 tulajdonság.
•
Az utolsó szint a kapcsolati szint, melyet az IWlanConnectionInfo interfész definiál. Tulajdonságai: - a network, mely a kapcsolódott hálózat nevét13 tartalmazza, - a secured, mely megmondja, hogy biztonságos-e a kapcsolat - és végül a signal tulajdonság, mely a jelerősséget tartalmazza.
10. ábra. Vezeték nélküli hálózat információk
12
Ha az interfész nem kapcsolódik hálózathoz, a tulajdonság értéke null variant. A könnyebb olvashatóság kedvéért nevezzük ezt a tulajdonságot így. A valóságban ez az érték megegyezik az SSID értékével. 13
29
3.9. Uptime Az Information osztály utolsó tulajdonsága az uptime, mely a rendszer elindulása óta eltelt időt tartalmazó karakterlánccal tér vissza, a felhasználó számára olvasható formátumban. Ehhez egyetlen függvényt, a GetTickCount-ot kell használni [12], mely a rendszer indulása óta eltelt időt adja vissza milliszekundumokban. Hogy a függvény kimenetét a felhasználó számára is értelmezhető formára konvertáljuk, pusztán azt kell tudnunk, hogy egy másodperc 1000 milliszekundumból áll (nyilván azt is érdemes tudni, hogy egy perc hány másodpercből áll, stb.). Ezek után az egyszerű függvényünk a következő (melyhez gondolom, nem kell különösebb magyarázat): function GetUptime: String; const TicksPerSecond = 1000; TicksPerMinute = TicksPerSecond * 60; TicksPerHour = TicksPerMinute * 60; TicksPerDay = TicksPerHour * 24; var tmp, Days, Hours, Minutes, Seconds: Cardinal; begin tmp := GetTickCount; Days := tmp div TicksPerDay; Dec(tmp, Days * TicksPerDay); Hours := tmp div TicksPerHour; Dec(tmp, Hours * TicksPerHour); Minutes := tmp div TicksPerMinute; Dec(tmp, Minutes * TicksPerMinute); Seconds := tmp div TicksPerSecond; Result := Format('%dd %dh %dm %ds', [Days, Hours, Minutes, Seconds]); end;
7. forrás. A bekapcsolás óta eltelt idő karakterlánccá konvertálása Érdemes megemlíteni, hogy GetTickCount az eltelt időt egy 32 bites előjel nélküli egész típusú változóban tárolja (DWORD). Ebből fakadóan 49,7 nap után a számolás elölről fog kezdődni.
30
4.
FRONT-END RÉSZ
A mini-alkalmazás második része a front-end rész, mely biztosítja a felhasználó számára az olvasható kimenetet. Mint az már az előző fejezetben kiderült, a tényleges információk megszerzését a háttér végzi, így a front-end rész feladata egy felhasználói felület biztosítása.
4.1. Alapok A Widget Engine XML-t használ mini-alkalmazások és annak objektumainak definiálásához. Az XML segítéségével tervezésidőben adhatjuk meg a létrehozandó objektumok típusát, a példányainak tulajdonságait, esemény kezelőit, stb., valamint az objektumoknak az egymáshoz való viszonyát. Megjegyzendő, hogy futásidőben is létrehozhatunk objektumokat, valamint beállíthatjuk az egymáshoz való viszonyukat (amennyiben szükséges). A létrehozandó objektumoknak két típusát különböztetjük meg. Az első típusba az egyszerű, gyermekkel nem rendelkező objektumok tartoznak. Ezeket az objektumokat mind tervezésidőben (amennyiben tervezésidőben definiáljuk, akkor tipikusan egy összetett objektum gyermekei), mind futásidőben is szokás létrehozni. A második típusba az összetett, gyermekekkel is rendelkező objektumok tartoznak. Ezeket tipikusan csak tervezésidőben, az XML fájlban szokás létrehozni, és főbb attribútumait beállítani. Egy mini-alkalmazás betöltődésekor a motor betölti az XML fájlt, majd létrehozza a bent definiált objektumokat. A két típusba tartozó objektumok bármelyikének lehetnek metódusai is, melyek hívására már scriptfájlt kell írnunk (4.4. fejezet).
4.2. Gyermekkel rendelkező objektumok Ezen összetett objektumokat úgy lehet a legegyszerűbben elképzelni, mintha egy alaposztályhoz (a gyermekkel rendelkező objektum típusa) új mezőket (gyermekek) vennénk fel, majd ezt az új osztályt példányosítanánk. Az egyetlen különbség, hogy az így létrehozott példányokhoz futásidőben is felvehetünk új gyermekeket (az előző példánál maradva mezőket). Az összetett objektumok is rendelkezhetnek tulajdonságokkal, eseményekkel, stb., melyeket beállíthatunk tervezés- (XML fájl) és futásidőben (JavaScript kódból) egyaránt. Lássunk most erre egy sematikus példát: … <egyszeruObjektumTipus> …
8. forrás. Összetett objektum sematikus XML váza 31
Mint az már a korábbiakban is említésre került, lehetőség van futásidőben is gyermekek felvételére. Ehhez futásidőben az összetett objektum appendChild metódusát kell hívnunk: osszetettObjektum.appendChild(e gyszeruObjektum vagy osszetettObjektum);
9. forrás. Az appendChild metódus Futásidőben gyermekek elérése a childNodes nevű tulajdonságon keresztül történik (aki foglalkozott már valaha XML-el, annak ismerős lehet a fenti kifejezés), mely egy tömbtulajdonság, és egyes elemei az egyes gyermekek. Összetett objektumtípusok a következők: Canvas, Flash, Frame, Image, Text, TextArea, ScrollBar, Web és Window. A dolgozatnak nem célja ezen objektumtípusok bemutatása, további információért látogassuk meg a Widget Engine DOM referenciáját. [1] Egy egyszerű mini-alkalmazásnak, látható végeredmény érdekében, a fenti típusok közül legalább kettőt kell definiálnia tervezésidőben, egy Widget és egy Window típusú objektumot: 4.2.1. widget objektum Ez egy speciális típusú objektum, mely a tényleges mini-alkalmazásunkat, és annak főbb alkotóelmeit (ablakok, időzítők, stb.) reprezentálja. Ez az objektumtípus is rendelkezik gyermekekkel (Timer, Window, stb.), így a gyermekekkel rendelkező objektumok közé kell, hogy soroljuk, ám kezelése és néhány jellemzője eltér a fentebb általánosságban bemutatott összetett objektumoktól. Nézzük most ezeket kicsit részletesebben: Egy mini-alkalmazás írásakor (XML fájl) mindenképp definiálnunk kell egy Widget objektumot. Az objektumot kizárólag tervezésidőben lehet definiálni, és az összes többi tervezésidőben definiált objektumnak a gyermekének (vagy a gyermekének a gyermekének, stb.) kell lennie. Nincs name tulajdonsága (futásidőben egyszerűen widget néven hivatkozhatunk rá), tervezéskor csak a minimumVersion (minimum motorverzió) attribútumot szokás beállítani. <w idget minimumVersion="1.5"> ...
10. forrás. A widget objektum definiálása Írható tulajdonságainak (az dockOpen vagy a visible tulajdonsága például csak olvasható, JavaScript kódból) beállítása speciális módon, a Settings XML elemmel történik (a 4.0-nál korábbi motorok esetén a „ertek” elemmel lehetett egy-egy beállítás értékét megadni).
11. forrás. A widget objektum beállításinak megadása Az objektum eseményeinek kezelése két módon történhet. Egyfelől futásidőben (erre példát a 4.4. fejezetben látunk), másfelől tervezésidőben speciálisan az Action elemmel:
12. forrás. Eseménykezelő beállítása tervezésidőben A fenti példa a widget egy eseményének tervezésidőben történő kezelésén kívül azt is mutatja, miként használhatjuk az onLoad eseményt scriptfájlok widget-hez történő csatolására. Két ok miatt érdemes a widget betöltődésekor egy fő script fájlt felcsatolni (bár nem kötelező). Egyfelől a mini-alkalmazás betöltődésekor a fő script fájl (példánál maradva a mainScript.js), és az általa becsatolt (include) újabb scriptfájlok törzsében található függvényhívások automatikusan lefutnak, melyeket használhatunk inicializáláshoz, vagy további eseménykezelők beállításához, stb. Másfelől így már hivatkozhatunk egy becsatolt JavaScript fájlban deklarált függvényre, események tervezésidőben történő kezelésekor (15. forrás). A dolgozatnak nem célja a widget objektum összes beállításának és eseményének bemutatása. További információt a referencia oldalakon [1] találhatunk. A widget objektum gyermekeire futásidőben (JavaScript fájlban) egyszerűen nevük alapján hivatkozunk, a szülő objektum nevét nem kell a hivatkozás előtt szerepeltetnünk (objektum.metodus). A gyermekeket futásidőben nem módosíthatjuk, ennek megfelelően az objektumnak sem appendChild metódusa, sem childNodes tulajdonsága nincs. Érdemes megjegyezni, hogy egyes egyszerű objektumtípusokat (4.3. fejezet) is csak tervezésidőben lehet definiálni. Ezeknek az objektumoknak mindenképp a widget objektum gyerekeinek kell lenniük. Ezek tipikusan a Preference, PreferenceGroup és About-Box típusok. Az előző típusokról bővebben a referenciaoldalon [1] olvashatunk. 4.2.2. window objektum A mini-alkalmazás egy ablakát reprezentáló objektum (Window). Az összes megjeleníteni kívánt vizuális komponensnek (Frame, Image, Text, stb.) egy ablak gyermekének kell lennie. Ez a típus már teljes egészében megfelel a fentiekben általánosan bemutatott összetett objektum-típusnak. Ennek megfelelően a tervezésidőben definiált objektumok name tulajdonságát is be kell állítanunk, amennyiben futásidőben is el szeretnénk azt érni. Mivel az objektumnak már van appendChild metódusa, így futás időben is felvehetünk új gyerekeket (tipikusan új megjeleníteni kívánt Image vagy Frame objektumot). Tulajdonságainak és eseményeinek beállítása már a bevezetőben bemutatott módon történhet tervezésidőben:
13. forrás. Egy Window objektum definiálása tervezésidőben Futásidőben az események és tulajdonságok beállítása is a szokott módon történik.
4.3. Gyermekkel nem rendelkező objektumok A tervezésidőben definiálható objektumok második típusa az egyszerű, gyerekkel nem rendelkező objektumok csoportja. Ezen objektumok mindig egy összetett objektum gyermekei (tervezésidőben), és a következőképp kell őket definiálni: <egyszeruObjektumTipus> …… <esemeny1> <esemeny2>
14. forrás. Egyszerű objektum sematikus XML váza Az így létrehozott objektumnak van name tulajdonsága, melyet érdemes beállítani, hogy később futásidőben is elérhessük azokat. Mint az már említésre került, objektumokat futásidőben is létrehozhatunk, vagy egy tervezésidőben létrehozott objektumot módosíthatunk. Lássunk most ezekre egy-egy egyszerű példát. Hozzunk létre egy időzítőt (a lehetséges objektumtípusokról később) először tervezésidőben (a mini-alkalmazásnak is van főidőzítője): mainTimer1true
15. forrás. Egy Timer objektum definiálása Az onTimerFired eseményt kétféleképp kezelhetjük az XML fájlban. Az első módszer, melyet az első sematikus példa is mutat, hogy a teljes eseménykezelő függvényt definiáljuk. A második példában látható, hogy csak egy függvényhívás történik. Ebben az esetben, a példánál maradva, az update() függvényt valamely becsatolt (include) JavaScript fájlnak tartalmaznia kell. Ezzel részletesen a korábbiakban foglalkoztunk. Most lássuk a tervezésidőben létrehozott időzítő tulajdonságainak módosítását futásidőben, JavaScript kódból: mainTimer.interval = 10; mainTimer.ticking = false; mainTimer.onTimerFired = function() {alert(”timer fired!”)}; //esemenykezelo beallitasa
16. forrás. Tulajdonságok módosítása tervezésidőben
34
Végül lássuk, miként lehet létrehozni egy időzítőt futásidőben: var new Timer = new Timer(); newTimer.interval = 1; newTimer.ticking = true;
17. forrás. Egy Timer objektum létrehozása futásidőben Egyszerű objektumtípus a fentiekben bemutatott Timer, HotKey, Preference (csak tervezésidőben), MenuItem, stb.. Az egyszerű objektumtípusokról bővebben a Widget Engine DOM referenciájában olvashatunk. [1]
4.4. Egy egyszerű mini-alkalmazás Most, hogy megnéztük, mely típusok szükségesek mindenképp egy mini-alkalmazás létrehozásához, lássunk egy egyszerű példát: <w idget minimumVersion="4.0"> <settings> <setting name="debug" value="on"/> <w indow name="mainW indow" title="Sample" width="500" height="500">
18. forrás. Egy egyszerű mini-alkalmazás XML definíciója És most lássuk az előző XML fájlhoz tartozó mainScript.js fájlt. Init(); //az onLoad esemeny bekovekeztekor amutomatikusan hivasra kerul //esemenykezelo beallitasa w idget.onUnload = function() {alert(”A minialkalmazás leáll!”);} function Init() {alert(”A minialkalmazás elindult!”);}
19. forrás. Egy egyszerű script fájl az egyszerű mini-alkalmazáshoz
4.5. A meta-adatfájl A widget.xml fájl a preferált út a mini-alkalmazás meta-adatainak megadásához. [1] Lényegében a fájl a korábbiakban már bemutatott widget objektum néhány statikus tulajdonságát állítja be. Lássunk most egy példát a meta-adatfájlra:
35
<metadata> <description> < description>
20. forrás. A meta-adatfájl váza Ha összehasonlítjuk a widget objektum tulajdonságait és a fenti XML fájlt, látható, hogy az egyes tulajdonság- és levélnevek között átfedés van. Az azonos nevű tulajdonságok a widget objektumban csak a visszamenőleges kompatibilitás miatt vannak jelen, és az új mini-alkalmazásokban e tulajdonságok értékeit a meta-adatfájlban kell beállítani. A mini-alkalmazás azonosítója egy GUID, mellyel egyértelműen azonosítható a widget, és melynek a Widget Engine mini-alkalmazásfrissítő szolgáltatásánál vehetjük hasznát. A meta-adatfájl további feladata biztonsági jellegű. Ezt az XML elemet security elemenek nevezzük, és feladata biztonsági jogosultságok hozzárendelése a minialkalmazáshoz. Jogosultságok beállítása formailag a következőképp néz ki: <security> <sec_item1> value <sec_item2> value
21. forrás. Jogosultságok beállításának váza A lehetséges jogosultságokat pedig az alábbi táblázat mutatja: Elem
Lehetséges értékek none, sandbox (hozzáférés csak a widget könyvtárhoz) filesystem • user (hozzáférés a felhasználó saját mappáihoz) • full com GUID vagy COM objektum név • true (runCommand és runCommandlnBg command használható) • false 1. táblázat. A lehetséges jogosultságok • •
36
4.6. A mini-alkalmazás fájlstruktúrája Egy mini-alkalmazás fájl struktúrájának a következő sémát kellene [1] követnie: myWidget.widget Contents widget.xml myWidget.kon Resources Mivel a kész mini-alkalmazás összes fájljának egy *.widget nevű fájlban kell lennie, ezért a fenti fájlokat valamely módon egy fájlba kell szervezni. Erre a klasszikus módszer, hogy a fájlstruktúrát egy zip fájlba csomagoljuk (vagy használjuk a Widget Converter mini-alkalmazást). Ebben az esetben a widget betöltésekor annak tartalma kitömörítésre kerül a „Documents and Settings\User\Local Settings\Yahoo\Widget Engine\Unzipped” útvonal alá. Az újabb módszer az úgynevezett flat-file formátum, mely a motor saját formátuma. A formátum nem tömörít, így hatékonyabb a szoftver, ám a fájl mérete is nagyobb lesz. Mivel jelen esetben a mini-alkalmazás egyedi bináris állományokat is használ (backend, stb.), és ezen állományok kezelése körülményes az újabb formátumból, így maradtam a tömörített formánál.
4.7. Újítások a frontend részben A mini-alkalmazás front-end része (is), az újragondolását követően, számos részben módosításra került. Ezen újítások egy részét maguk a felhasználók kérték még az eredeti widget készítőjétől, más részeket én találtam hasznosnak bevenni az új projektbe. Lássuk most ezeket először futólag, aztán majd később részletesen is, jelezve melyik újításnak mi volt az előzménye: 1. Dokkolás támogatása: A 4.0-ás motortól felfelé lehetőség van a minialkalmazások dokkolására, és a dokk animálására. Ezt a funkciót a legtöbb újonnan létrehozott mini-alkalmazás alapszinten legalább már támogatja (ám támogatása az eredeti widget-ben teljes egészében hiányzott), így célszerű volt bevenni az újítások közé. 2. Magonkénti CPU információ: A mini-alkalmazás egyik fő újítása. Erőforrás monitorozáskor érdemes a CPU aktivitást, sebességet, stb. magokra lebontani. Ezt a funkciót a felhasználók is kérték az eredeti mini-alkalmazás készítőjétől. A korábbiakban már bemutattam, hogy e funkció megvalósításához a bac-kend-et is módosítani kellett. 3. Lemezteljesítmény monitorozás: A második fő újítás a lemezteljesítmény monitorozása. Mint ahogy érdemes tisztában lenni a rendszer által generált hálózati forgalommal, úgy érdemes a lemezaktivitást is szem előtt tartani. Ezt a funkciót az ihlette, hogy még magában a Windows operációs rendszerben is 37
4.
5.
6.
7.
8.
csak körülményesen lehet hozzáférni a lemezteljesítmény adatokhoz. Ez a funkció szintén a COM kiszolgáló módosítását igényelte. Eszközönkénti monitorozás: A mini-alkalmazás egy újabb hiányosságát pótolja ez a tulajdonsága. A felhasználónak lehetősége van mind a hálózati aktivitás, mind a lemezaktivitás mérésekor a monitorozandó eszköz (hálózati interfész / lemezmeghajtó) kiválasztására (részleteket lásd az egyedi beállítások és hálózati interfészek fejezetben). Testreszabhatóság: A felhasználói visszajelzések alapján igény volt rá, hogy testre lehessen szabni a mini-alkalmazás küllemét (mely grafikonok jelenjenek meg). A motor animációs API-jának használata: Az előző ponthoz kapcsolódik ez az újítás. Egy-egy grafikon ki- vagy bekapcsolásakor érdemes egy szép animációt is társítani az eseményhez. Ez az újítás hasznosnak épp nem mondható, de annál látványosabb. WiFi monitorozása: A motorban megtalálható hiányos wireless objektumot lecseréltem a háttéralkalmazás saját (hasonló nevű) objektumára, ezzel olyan új információkhoz juthatunk a vezeték nélküli hálózatokról, melyeket, ha csak a motorra hagyatkoznánk, nem tudnánk meg (hálózati interfész neve, kapcsolat biztonságossága). A widget egyik újítása a vezeték nélküli hálózatok monitorozása. Ez a funkció mindenképp hasznos lehet a hordozható számítógéppel dolgozók számára. Teleptöltöttség mérés: A motor támogatást nyújt teleptöltöttség-információk megszerzésére. Ezt használva egy újabb funkcióval bővítettem a minialkalmazás tudáspalettáját.
Látható, hogy egyes új tulajdonságok bevezetéséhez erősen módosítani kellett a háttéralkalmazást is. Ezekkel már a korábbiakban foglalkoztam, így most csak utalni fogok rájuk, és a figyelmet a script részben (az XML részben, új objektumok felvételén kívül más módosítás nem történt, így annak bemutatásától most eltekintek) történt módosításokra összpontosítom. Megjegyzendő, hogy nem fog bemutatásra kerülni sorról sorra az összes módosított kódrészlet, mivel ennek nem sok gyakorlati haszna volna. Ehelyett az újítások főbb jellemvonásait és eljárásait veszem górcső alá. 4.7.1. Dokkolás támogatása A Widget Engine 4.0-tól kezdve egy új funkcióval bővült, a mini-alkalmazások egy helyen történő kezelésére bevezetett dokkolással. A dokk egy külön ablakban mutatja a képernyő valamely sarkán az éppen futó mini-alkalmazásokat (vagy épp az összes telepítettet). Új mini-alkalmazás írása során opcionálisan lehetőségünk van vezérelni, hogy a mini-alkalmazáshoz kapcsolódó dokk elemben mi jelenjen meg14. A dokk elem mérete 75X70 pixel. Ezen a területen opcionálisan szöveget (Text), képet (Image) vagy frame-et (Frame) jeleníthetünk meg.
14
Amennyiben a mini-alkalmazás nem támogatja a dokkolást, a dokk ablakban csak egy statikus ikon jelenik meg, mely a mini-alkalmazást reprezentálja.
38
A megjeleníteni kívánt információkat kizárólag XML dokumentumként közölhetjük a motorral. Ehhez a widget objektum setDockItem metódusát kell használnunk. Bár első ránézésre úgy tűnik, hogy így csak statikus információkat jeleníthetünk meg (egy XML fájlba írjuk tervezés időben a megjeleníteni kívánt adatot), ám egy kis trükkel ez a probléma megkerülhető. [1] A megoldás lényege, hogy tervezésidőben az XML fájlba, a létrehozandó objektumoknak csak a statikus információit írjuk (szövegek, képek deklarálása, azok elhelyezkedése a 70X75 méretű területen, stb.): <dock-item version="1.0">
22. forrás. Statikus információk egy dokkelem beállításához Látható, hogy a szovegNev nevű Text objektumnak csak az elhelyezkedése és a stílusa van beállítva, a data tulajdonság például hiányzik. Ha most olvasnánk be az XML fájlt, és adnánk át a dokumentumot, akkor a dokk elemben semmi sem jelenne meg. Ezért, miután egy XML dokumentumba olvastuk a fájl tartalmát, az XMLDOM API-t hívjuk segítségül, mellyel már módosíthatjuk a dokumentumot (nem lesz hatása a fájlra), így például új attribútumokat adhatunk hozzá, ezzel a létrehozandó objektum egy új tulajdonságát beállítva (a definiált objektum, csak a setDockItem metódus hívásakor jön létre). A dolgozatnak nem célja az XMLDOM API bemutatása, így feltételezzük, hogy az olvasó már valamilyen szinten tisztában van az API metódusaival. [13] Lássuk most az előző példa folytatását (tegyük fel, hogy az előző példában szereplő fájl neve vitality.xml): var vitality = XMLDOM.parse(filesystem.readFile("vitality.xml")); var szoveg = vitality. getElementById("szovegNev"); szoveg.setAttribute("data", "ez az új megjelenítendő szöveg márt látszani fog!");
23. forrás. Dinamikus információk beállítása egy dokk elemhez Ezek után az XML dokumentum (ha fájlba írnánk) a következőképp festene: <dock-item version="1.0">
24. forrás. A módosított dokumentum
39
Látható, hogy most már az XML fájl alapján létrehozandó Text objektum data tulajdonsága is ki van töltve, ennek megfelelően már hívhatjuk a dokumentumra a setDockItem metódust (a példánál maradva widget.setDockItem(vitality)). Érdemes szem előtt tartani, hogy a komplett dokumentumra hívjuk a metódust, és ne csak a módosított elemre! A metódus visszatérése után definiált objektum létrejön és megjelenik a megadott helyen, valamint ezzel egy időben a metódus előző hívása során létrehozott objektumok törlődnek a memóriából. A mini-alkalmazás jelen verziójában a dokkot az összesített CPU aktivitás és memória kihasználtság monitorozására használom. Látható, hogy ehhez két Text objektumra lesz szükség, melyeket egymás alá érdemes elhelyezni, középen. <dock-item version="1.0">
25. forrás. Vitality.xml A két Text objektum adatait a cpu és memory objektumok segítségével definiáltam. A cpu objektum activity tulajdonsága a CPU kihasználtságot mutatja, az összes használatban lévő magon, ami épp megfelelő a feladatra. Ezek tükrében a kód a következő: var dock = XMLDOM.parse(filesystem.readFile("vitality.xml")); var dockCpu = dock.getElementById("cpu"); var dockRam = dock.getElementById("ram"); function updateDock() { if (!w idget.dockOpen) return; dockCpu.setAttribute("data", "CPU: " + system.cpu.activity + "%"); dockRam.setAttribute("data", "RAM: " + system.memory.load + "%"); w idget.setDockItem(dock); }
26. forrás. Dokkelem frissítése futásidőben A fenti kódból is jól látható, érdemes ellenőrizni, hogy a dokk meg van-e nyitva, mielőtt azt frissítenénk. A kód megírása után, íme, az eredmény (amennyiben a motorban a dokkolás engedélyezve van):
40
11. ábra. A létrehozott dokkelem a mini-alkalmazásban 4.7.2. A Graph osztály A mini-alkalmazás a monitorozott eszközökről az adatokat grafikonon jeleníti meg. Ennek gyakorlati haszna, hogy láthatjuk visszamenőlegesen is a mért aktivitást. Ezt a megoldást hasznosnak találtam, ám némileg módosítani kellett (itt is) az eredeti programot. Lássuk most a grafikonok működési elvét, kitérve az egyes újításokra: Adott n darab kép (Image), melyek mindegyike egy-egy pixel-t (a darabszám határozza meg a grafikon hosszát) ábrázol. A képek magassága15 kezdetben 0, szélessége állandóan 1. Az n darab képet egy tömbbe (a későbbi módosításhoz) és egy frame-re (Frame) érdemes helyezni, úgy, hogy a legelső kép a frame bal oldalán, míg a legutolsó a frame jobb oldalán legyen. Ezzel a megoldással a későbbiekben elegendő lesz csak a frame pozícióját állítani (az animációhoz kell), és ezzel az egész grafikon a megfelelő módon fog elmozdulni. Megjegyzendő, hogy ez a technika szakít az eredeti widget által használt módszerrel, melyben a grafikonokat nem kellett futásidőben mozgatni. function Graph(height, w idth) { this.data = new Array(width); this.frame = new Frame(); this.frame.height = height; this.frame.w idth = w idth; this.frame.vAlign = "bottom"; for (i = 0; i < width; i++) { var bar = new Image(); bar.src = "resources/images/pixel.png"; bar.vAlign = "bottom"; bar.width = 1; bar.height = 0; bar.hOffset = i + this.frame.hOffset; bar.vOffset = height + this.frame.vOffset; this.data[i] = bar; this.frame.appendChild(bar); }
27. forrás. A Graph osztály konstruktora
15
Image objektumokban lehetőség van képek nyújtására, a magasság és szélesség állításával.
41
Grafikon frissítésekor bejárjuk a pixeleket tartalmazó tömböt, balról-jobbra, az utolsó előtti elemig úgy, hogy az aktuális pixel magassága, az őt követő pixel magassága legyen (ezáltal fog úgy tűnni, mintha a grafikon mozogna). A legutolsó pixel magassága pedig az új, frissítéskor átadott érték16 lesz (0-100). this.update = function(value) { value = value > 100 ? 100 : value; for (i = 0; i < w idth - 1; i++) this.data[i].height = this.data[i + 1].height; //scale = Math.r ound(100 / height) this.data[w idth - 1].height = Math.round(value / this.scale); }
28. forrás. Az update metódus Érdemes az osztályt két további metódussal is bővíteni. Az első a clear, melynek feladata nevéből adódóan a grafikon kinullázása (az eredeti osztályban nem szerepelt, új monitorozandó interfész vagy lemez választásakor érdemes az előző eszközről készült grafikont „kinullázni”). Ehhez csupán végig kell menni a pixeleket tartalmazó tömbön, és az összes kép magasságát 0-ra kell állítani. A második az eredeti osztályban is meglévő setColor metódus, mellyel a grafikon színét lehet beállítani. Itt is bejárjuk a tömböt és minden egyes pixel színét17 a megfelelő értékre állítjuk. this.clear = function() { for (i = 0; i < this.data.length; i++) this.data[i].height = 0; } this.setColor = function(color) { for (i = 0; i < this.data.length; i++) this.data[i].colorize = color; }
29. forrás. A clear metódus 4.7.3. A Chart osztály Az előzőekben már láttuk, miként lehet egy grafikont létrehozni. Jogos igény, hogy ennek a grafikonnak legyen háttere, címe, jelenjen meg magán a grafikonon is valamilyen szöveg, stb. Erre a feladatra lett létrehozva, a Plus-ban bemutatkozó Chart osztály. Érdemes megjegyezni, hogy az eredeti mini-alkalmazás is tudta már az osztály által megvalósított funkciókat, azonban azok sokkal körülményesebbek voltak, továbbá az eredeti megoldásnál maradva lehetetlen lett volna az animáció megvalósítása. Az osztály hat különböző egységből áll össze. Az előzőekben már bemutatott grafikonból (Graph), a háttérből18 (Image), mely előtt a grafikon és a többi objektum megjelenik, a címből, mely a grafikon felett található, a grafikon szövegéből (Text) és annak árnyékából (Text) és végül az ezeket mind birtokló frame-ből (Frame). Hogy az újonnan létrehozott Chart objektum szépen nézzen ki, néhány apróságot érdemes megtenni, melyeket az alábbi kód mutat be: 16
Amennyiben a grafikon magassága nem 100 pixel, figyelembe kell venni az arányosságot! Image objektumok rendelkeznek egy colorize tulajdonsággal 18 Mivel mindig az utoljára a tulajdonosra helyezett objektum lesz előtérben, ezért a háttér megvalósításához csupán az őt megvalósító képet kell először a tulajdonos frame-re, ablakra, stb. helyeznünk. 17
30. forrás. A Chart osztály egy példányának „finomhangolása” Látható, hogy csak a grafikon felett megjelenő szöveg helyét (címet) definiáltam. Ennek oka, hogy magán a grafikonon megjelenő szöveg mindig a grafikon közepére lesz igazítva, melyet egy külön metódus végez, így felesleges inicializáláskor beállítani a szöveg helyét. Ezek után, figyelembe véve a megjelenítés sorrendjét (a szövegek és képek milyen sorrendben fedjék el egymást), a következő sorrendben érdemes elhelyezni a fenti objektumokat a frame-en: this.frame.appendChild(this.background); this.frame.appendChild(this.graph.frame); this.frame.appendChild(this.title); this.frame.appendChild(this.textShadow); this.frame.appendChild(this.text);
31. forrás. A megjelenítés sorrendjének beállítása Itt is megfigyelhetjük, hogy minden objektum pozíciója a tulajdonos frame egyes pontjaihoz lett igazítva, így elég csak magát a frame-et mozgatni a későbbiekben, hogy minden rajta lévő elem is a megfelelő helyre kerüljön. Ennek tükrében most lássuk a metódust, mely mindig a grafikon közepére illeszti a grafikon szövegét. Ehhez először meg kell határozni, hogy az X tengelyen hol helyezkedjen el a szöveg, melyhez a háttér szélességéből kell levonni a szöveg szélességét19 (mekkora terület maradt üresen a X tengelyen, pixelekben), majd ezt a területet kell osztani kettővel (jobb és baloldalon is ugyanannyi rész maradjon üresen). Másodszor az Y tengelyen is meg kell határozni a szöveg pozícióját, melyhez a bal felső sarok Y koordinátájához kell hozzáadni annyi pixelt, ahány pixellel szeretnénk, hogy „belógjon” a grafikonba a szöveg. Az árnyékoknak érdemes csak egy pixelnyivel „kilógniuk” a szöveg alól. Így a metódus kódja a következő:
19
Vizuális objektumról lévén szó, szélesség tulajdonsága, mely pixelekben adja vissza a szöveg szélességét.
32. forrás. Az allignText metódus A metódust ezek után új szöveg beállítása, vagy szöveg betűtípusának megváltoztatása esetén kell hívni. Megjegyzendő, hogy a metódus nem kezeli azt az esetet, ha a szöveg szélessége nagyobb, mint a grafikon szélessége (későbbi verzióban ez kijavításra kerül). Ez után érdemes bevezetnünk a setTextFont és a setText metódusokat, hogy az alignText mindig a megfelelő időben legyen hívva.
12. ábra. A Graph és Chart osztályok Futásidőben a fentebb bemutatott két osztály egy példánya a következőképp néz ki:
13. ábra. Egy futásidőben létrehozott Chart objektum 4.7.4. Animáció a mini-alkalmazásban A Widget Engine 2.0-s verziójától kezdve lehetőség van animációk használatára. Az API lehetőséget ad JavaScript kódból történő egyedi animációk létrehozására. Négy különböző animáció típus létezik: az áttünés, mozgatás, átméretezés és végül a forgatás. Az animációkat az animation nevű objektum vezérli. Két módon indíthatunk animációt: szinkron és aszinkron módon: Az objektum start metódusa azonnal visszatér, és a program a következő soron folytatódik tovább, miközben az animáció elkezdődik, míg a runUntilDone metódus csak a kijelölt animáció befejeztével tér vissza. Mindkét metódus egy animációs objektumot, vagy animációs objektumokat tartalmazó tömböt vár paraméterül. Animációs osztály típusok reprezentálnak egy-egy támogatott animáció típust: MoveAnimation, FadeAnimation, ResizeAnimation, és RotateAnimation. Az osztályok példányosításakor a konstruktorban kell átadnunk paraméterül az animálandó objektumot (Frame, Image, Text vagy Window típusú objektumok engedélyezettek). A konstruktorok további paraméterei az animáció típusától függenek. Mivel a dolgozatnak nem célja a teljes API bemutatása, így most
44
csak a mini-alkalmazáshoz kapcsolódó objektumokkal és azok használatával foglalkozom. Az első ilyen osztály a FadeAnimation, mely az objektum áttetszőségét változtatja. A konstruktor négy kötelező és egy opcionális paraméterrel bír. Az első paraméter az animálni kívánt objektum. A második az „áttetszőségi szint” (0-255). Az animált objektum opacity tulajdonsága fog eddig a paraméterig változni. A harmadik paraméter az időintervallum, amennyi idő alatt az animáció lezajlik, milliszekundumokban. A negyedik paraméter csak akkor van értelmezve, ha az animator objektum ease metódusát használjuk, így ezzel a paraméterrel most nem foglalkozom. Az ötödik paraméter opcionális, és esetünkben nem is lényeges ezért elhagyjuk. var a = new FadeAnimation(myObject, 0, 350, animator.kEaseOut); animator.start( a );
33. forrás. A FadeAnimation példányosítása és használata A második osztály a MoveAnimation, mely egy objektumot mozgat adott X és Y koordinátákig az aktuális helyétől. Példányosításkor az osztály konstruktora öt kötelező és egy opcionális paraméterből áll. Az első itt is az animálandó objektum, a második az új X, a harmadik az új Y koordinátát adja meg. A negyedik paraméter adja meg az időintervallumot, milliszekundumokban, míg az ötödik és hatodik paraméter itt is lényegtelen. var a = new MoveAnimation(myObject, 100, 100, 350, animator.kEaseOut, MoveDone); animator.start( a );
34. forrás. A MoveAnimation példányosítása és használata A fenti két osztály példányai segítségével már szebbé tehető a mini-alkalmazás. A widget egyik újítása, hogy lehetőség van futás időben kiválasztani, mely grafikonok jelenjenek meg a főablakon. Az alapkoncepció az, hogy minden grafikonnak van egy előre definiált fix helye, ám ezek a helyek változhatnak, ha a felhasználó egy-egy grafikont engedélyez, vagy letilt. Például, ha kikapcsoljuk a RAM-ot monitorozó grafikont, az alatta lévő hálózati és lemezmonitorozó grafikonok feljebb „kúsznak”. Miközben ezek a grafikonok felfelé mozognak, a kikapcsolt grafikon teljesen áttetszővé válik (eltűnik). Az algoritmus a következő: Feltöltünk egy tömböt a lehetséges Y pontokkal, ahol az objektumok (grafikonok) elhelyezkedhetnek (fix helyek). Végigmegyünk az animálandó objektumok listáján. Ha egy objektum látszik, akkor létrehozunk egy új MoveAnimation objektumot, melynek Y tengelyre vonatkozó paraméterét a következő még nem objektumhoz rendelt fix Y helyre állítjuk (lehet, hogy marad az objektum az eredeti helyén). Ezután minden objektumhoz létrehozunk egy FadeAnimation objektumot. Ha az objektum látszik, akkor a láthatóságát az animációban 255-re állítjuk (nem átlátszó), ellenkező esetben 0-ra (láthatatlan). Előfordulhat, hogy egy objektum láthatósága nem változik. Befejezésül az összes új animációt elindítjuk egyszerre.
45
function fadeCharts(vOffsets, charts, values) { var animations = new Array(); var selector = 0; for (i = 0; i < values.length; i++) { var showChart = values[i]; if (showChart) { var a = new MoveAnimation( charts[i].frame, charts[i].frame.hOffset, vOffsets[selector], 350, animator.kEaseOut ); animations.push(a); selector++; } var a = new FadeAnimation( charts[i].frame, showChart ? 255 : 0, 350, animator.kEaseOut ); animations.push(a); } animator.start(animations); }
35. forrás. A mini-alkalmazás animációját megvalósító függvény A fenti függvényt az új beállítások érvénybelépésekor kell hívni, az összes grafikonra. 4.7.5. Magonkénti CPU monitorozás A back-end részről szóló fejezetben már bemutatásra került miként kellett a háttéralkalmazást módosítani ehhez a feladathoz, így ennek mikéntjére most nem térnék ki. A mini-alkalmazás elindításakor létrehozott COM objektum CPUs (simpleMonitor.cpus) tulajdonságára azonban emlékeztetnék. A tulajdonság egy csak olvasható listatulajdonság, mely egy egyszerű listával (ISimpleList) tér vissza, melynek elemei az IProcessorInformation interfészt (7. ábra) megvalósító objektumok. Ha visszalapozunk a fentebb hivatkozott ábrára, látható, hogy minden egyes magra megtalálhatók a szükséges információk. Megjegyzendő, hogy míg az aktivitást érdemes másodpercenként megvizsgálnunk, addig a processzor sebességet elég csak ritkábban, végül a processzor nevét elég csak egyszer lekérdeznünk (ezzel némileg növelve a teljesítményt). Ennek megfelelően két időzítőre volt szükségem. Egy gyorsabbra és egy lassabbra. A gyorsabb időzítő a mini-alkalmazás fő időzítője lesz (részleteket lásd az XML fájlban). Az időzítők definiálása után már megírhattam a függvényeket is, melyek a tényleges lekérdezést végzik. Az első gyakorlatilag az update függvény (fő időzítőhöz a frissítő függvény) része:
46
try { var cpuArray = simpleMonitor.cpus; for (i = 0; i < numProcessors; i++) { var cpu = cpuArray.item(i); activity[i] = cpu.activity; } } catch (e) { alert("Failed to get CPU activity: " + e); closeWidget(); }
36. forrás. Processzoraktivitás mentése Mivel a frissítés úgy zajlik, hogy először lekérdeztem az aktuális információkat a háttéralkalmazástól (a kiszolgáló által generált esetleges kivételeket itt kezelem), majd sikeres lekérdezés esetén frissítettem a grafikonokat, így először egy ideiglenes tárolóba kellett helyezni az új adatokat (hogy később azok segítségével frissíteni lehessen a grafikonokat). Látható, hogy a lekérdezéshez egy activity nevű tömbre volt szükség (melyet inicializáláskor definiáltam), mely az egyes magokhoz tartozó aktivitásokat tárolja. Észrevehetjük még azt is, hogy nem a lista count tulajdonságát használtam bejárás során. Ennek oka, hogy a főablakon maximum két processzor grafikonnak van hely, és a numProcessors változót inicializáláskor ennek megfelelően állítottam be (ha több magunk van, mint 2, akkor az értéke 2). Ezt követően már frissíthettem a grafikont (a processzor többi tulajdonságát az updateCpuInfo függvény szolgáltatja, mely inicializáláskor egyszer már lefutott, így a lassú időzítő első onTimerFired eseménye előtt is már van komplett processzorinformációnk). function updateCpuInfo(full) { try { var cpuArray = simpleMonitor.cpus; if (full) { for (i = 0; i < numProcessors; i++) { var cpu = cpuArray.item(i); processorSpeed[i] = cpu.speed; processorName[i] = cpu.name; } } else { for (i = 0; i < numProcessors; i++) { var cpu = cpuArray.item(i); processorSpeed[i] = cpu.speed; } } } catch (e) { alert("Failed to update CPU info: " + e); closeW idget(); } }
37. forrás. Egyéb processzorinformációk mentése A függvény figyelembe veszi a fentebb már említett teljesítmény-központúságot. Ennek megfelelően a full paramétere megmondja, hogy teljes vagy csak egyszerű lekérdezést szeretnénk csinálni. Teljes lekérdezést a program inicializálásakor kell egyedül végrehajtani (csak egyszer kérdezzük le a processzor nevét), az első onTimerFired esemény előtt. A későbbiekben elég csupán már a sebességet lekérdezni (a lassú időzítő eseményében). A frissített adatok magonként a megfelelő 47
processorSpeed vagy processorName nevű tömbbe kerülnek (ezeket a tömböket szintén inicializáláskor deklaráltam). Végül mivel már minden szükséges információ rendelkezésre áll a grafikon frissítéséhez, hívható a grafikon frissítő függvény. Megjegyzendő, hogy a két maghoz tartozó két grafikon változójának neve cpu1Chart és cpu2Chart (így értelme van az eval függvény hívásának). function updateCpuGraphs() { var i = 0; do { var cpuChart = eval('cpu' + i + 'Chart'); cpuChart.setTitle("CPU" + (i + 1) + ": " + activity[i] + "%"); cpuChart.setText("Speed: " + processorSpeed[i] + " MHz"); cpuChart.graph.update(activity[i]); if (cpuChart.background.tooltip != processorName[i]) cpuChart.background.tooltip = processorName[i]; } while (++i < numProcessors); }
38. forrás. A processzoraktivitást mutató grafikonok frissítése 4.7.6. Selector ablak és az egyedi beállítások Egy mini-alkalmazás opcionálisan tartalmazhat felhasználói beállításokat is. Ezen beállítások nevét és típusát tervezésidőben szokás definiálni (*.kon fájlban) a preferenceGroup és preference típusú objektumokkal. Ekkor a felhasználói beállítások is megjelennek a widget beállításai közt. Ezen új, egyedi beállítások is a regisztrációs adatbázisban tárolódnak. A widget feladata az eszközönkénti monitorozás is, ahol futásidőben a felhasználó kiválaszthatja, mely eszköz legyen monitorozva (hálózati adapter, lemezmeghajtó). A mini-alkalmazás futása során folyamatosan frissítjük a monitorozható eszközök listáját (bővebben lásd hálózati adapterek és lemezmeghajtók fejezetben), ezért ezt a listát nem lehet tervezésidőben definiálni. Eszközök kiválasztására a preference típusú objektum típusát „popup”-ra kell állítani (más programozási nyelvekből ezt ComboBox-ként ismerhetjük). Ez tervezés és futás időben a következőképpen néz ki: preference1.name = "popname"; preference1.title = "Options: "; preference1.type = "popup"; preference1.option = options; //array preference1.optionValue =optionValues; //array preference1.defaultValue = defaultValue;
39. forrás. Popup objektum futásidőben <preference name="colorPref" type="popup">
40. forrás. Popup objektum tervezésidőben
48
Kezelhetőségi okokból azonban ezeket a ComboBox-okat külön ablakban szerettem volna megjeleníteni. A probléma, hogy ebben az esetben a kiválasztott opció nem lesz automatikusan mentve, és nem is lehet azt később olvasni, valamint a kezelőfelületet is meg kellett valósítani (a fenti beállításoknak a motor automatikusan biztosít egy formot, melyen a beállítások megjelennek). A feladatot két lépésben tudtam megoldani. Először egy új beállítás kezelőt kellett írni, mely az új form-on lévő beállításokat menti el. Lássuk először ennek mikéntjét: 4.7.6.1. A Settings osztály Mivel a widget-ből nincs lehetőség a regisztrációs adatbázis módosítására vagy olvasására, így más módszert kellett találni. Egy lehetséges megoldás az új beállítások XML fájlba történő mentése, melyhez a motor az XML DOM API-t biztosítja [13]. A dolgozatnak nem célja a teljes API bemutatása, így most csak a használt osztályokra és az algoritmusra összpontosítok. A Settings osztály feladata beállítás csoportok (hasonlóan a preferenceGroup-hoz) létrehozása és kezelése. Ennek megfelelően az osztálynak két metódusa van: A createGroup és a save. Az osztály konstruktorában átadom az XML fájl nevét, melyben a beállításokat tárolni szeretném (érdemes az XML fájl elérési útvonalának a mini-alkalmazás adat mappájának „Widget Data Folder” beállítani). A konstruktor megnézi, hogy a fájl létezik-e. Amennyiben létezik, az XMLDOM objektum parse metódusával megpróbálja értelmezni azt, azaz a fájlból létrehozni egy XML dokumentumot (továbbiakban dokumentum), amennyiben nem létezik, az XMLDOM objektum createDocument metódusával létrehoz egy új dokumentumot. Ezek után a fentebb bemutatott két metódus már dolgozhat a dokumentummal. Érdemes tudni, hogy a dokumentum is egy objektum, így rendelkezik tulajdonságokkal és metódusokkal. A save metódus, nevének megfelelően, a fenti dokumentumot a konstruktorban megadott fájlba menti (tipikusan a widget bezárásakor szokás hívni). Ehhez a dokumentum toXML metódusát használja. A createGroup metódus rendelkezik egy name paraméterrel. Hívásakor először a metódus ellenőrzi, hogy az XML dokumentumban létezik-e a name nevű levél. Ha nem, létrehoz egy levelet és hozzáfűzi a dokumentumhoz. Ezek után a dokumentum name leveléből már létrehozhat egy Props típusú objektumot (4.7.6.2. fejezet).
49
function Settings ( filename ) { this.doc = filesystem.itemExists( filename) ? XMLDOM.parse ( filesystem.readFile( filename)) : XMLDOM.createDocument(); this.save = function() { if ( filesystem.itemExists( filename)) filesystem.remove ( filename ); filesystem.w riteFile( filename , this.doc.toXML ()); } this.createGroup = function( name ) { var buf = this.doc.evaluate( name ); var groupNode = buf.length != 0 ? buf.item( 0 ) : this.doc.appendChild (this.doc.createElement(name)); return new Props( groupNode ); } }
41. forrás. A Settings osztály forrása
14. ábra. A Settings osztály 4.7.6.2. A Props osztály Az osztály egy példányának feladata egy beállítás csoportot reprezentáló XML levél kezelése. Egy beállítás csoport 0..n darab tulajdonság-érték párból áll. Egy ilyen levél sematikusan a következőképp néz ki: <property name="tulajdonsag1">ertek1 ... <property name="tulajdonsagN">ertekN
42. forrás. Beállításokat tartalmazó levél E párok kezeléséhez az osztály a get és a set metódust definiálja. Mindkét metódus az XPath [14] segítéségével éri el a levél különböző elemeit. A get metódus adott tulajdonsághoz tartozó értéket a következő paranccsal éri el a csoportot reprezentáló levélben: „property[@name='" + name + "']”, ahol a name a tulajdonság neve, mely tulajdonság értékére kíváncsiak vagyunk. A set metódus kicsit összetettebb. A metódusnak két paramétere van: a name és a value, mely a beállítani kívánt tulajdonság nevét és értékét tartalmazza. Amennyiben a name nevű tulajdonság már létezik, a metódus felülírja az értéket, ellenkező esetben létrehozza azt a csoportot reprezentáló levélben. Ez algoritmikusan a következő: Először a metódus létrehoz egy elemet, a dokumentum createElement metódusával, melynek neve „property” lesz (lásd előző ábra). Ezután ebben az elemben létrehoz egy „name” nevű attribútumot, az elem setAttribute metódusával. Ekkor az elem a következőképp fest: „<\property name=”tulajdonsag”>”. Ez után létre kell hozni egy szöveges levelet (textNode), a tulajdonság értékével, a dokumentum createTextNode 50
attribútumával, majd össze kell fűzni a két levelet, az új elem appendChild metódusával. Ezt követően az ismerős „<property name="tulajdonsag">ertek” sorhoz jutunk. Befejezésül a get metódushoz hasonlóan a set is megnézi, hogy a tulajdonság létezik-e már a levélben. Amennyiben igen, akkor felülírja a megfelelő elemet, ellenkező esetben hozzáadja az új elemet a levélhez. Mivel minden levél és új elem a dokumentum metódusaival lett létrehozva vagy módosítva, így a beállítások mentésekor elég a dokumentumot fájlba írni. function Props(groupNode) { this.name = function() { return groupNode.nodeName; } this.get = function(name) { var buf = groupNode.evaluate("property[@name='" + name + "']"); return buf.length != 0 ? buf.item(0).firstChild.data : ""; } this.set = function(name, value) { var prop = groupNode.ownerDocument.createElement("property"); var val = groupNode.ownerDocument.createTextNode(value); prop.setAttribute("name", name); prop.appendChild(val); var buf = groupNode.evaluate("property[@name='" + name + "']"); if (buf.length != 0) groupNode.replaceChild(prop, buf.item(0)); else groupNode.appendChild(prop); } }
43. forrás. A Props osztály forrása
15. ábra. A Props osztály 4.7.6.3. A Selector osztály A monitorozni kívánt interfész vagy lemez kiválasztásakor a lehetőségek listáját egy új form-on, egy legördülő menüvel szerettem volna megjeleníteni. Az előzőekben már foglalkoztam az egyedi beállítások elmentésének módszerével, most lássuk, miként tudunk egyedi ablakokat és vezérlőket létrehozni. Mivel a motor elsődlegesen nem vezérlők és form-ok létrehozására lett tervezve, ezért viszonylag szegényesen állnak rendelkezésre lehetőségek. Az egyik ilyen lehetőség a form függvény, mely kimondottan beállítások dialógusához hasonló ablakok létrehozásáért felel. Első paramétere egy tömb, mely a form-on elhelyezendő vezérlőket tartalmazza (a tömb első eleme kerül az ablak legmagasabb pontjára, a legutolsó a legalacsonyabbra), melyek elhelyezését a motor automatikusan elvégzi. A tömb 51
elemeinek FormField típusú objektumoknak kell lenniük (melynek ugyanazok a paraméterei, mint a pereference típusú objektumoknak tervezésidőben). A függvény második paramétere az ablak címét adja meg, míg a harmadik és negyedik a megszakító (mégse) és elfogadó (ok) gombok címkéjét. Amennyiben a párbeszédpanel elfogadó gombjára kattint a felhasználó, a függvény visszatérése is egy tömb, melynek egyes elemei az egyes vezérlők állapotát tartalmazzák (megjegyzendő, hogy ezen állapotok sem kerülnek mentésre az ablak bezárásakor, így ezeket is az előzőekben bemutatott módszerrel menteni kell). var formfields = Array(); formfields[0] = new FormField(); formfields[0].description = 'This is a description of a text field.'; formfields[0].name = 'name1'; formfields[0].type = 'text'; formfields[0].title = 'Text Pref Title'; formfields[0].defaultValue = 20; formfields[1] = new FormField(); formfields[1].title = 'Basic Field'; formfields[3] = new FormField(); formfields[3].name = 'name4'; formfields[3].title = 'Checkbox Pref Title'; formfields[3].type = 'checkbox'; formfields[3].defaultValue = 1; formfields[3].description = 'This is a description of a checkbox field.'; formResults = form(formfields, 'my title', 'Save It And Continue'); if (formResults != null) { print("formResults = " + formResults); } else { print("form w as cancelled"); }
44. forrás. Egy egyedi ablak létrehozása Jelen esetben a form-on csak egyetlen vezérlő (ComboBox) lesz. A form bezárásakor a show metódus megvizsgálja, hogy új érték lett-e kiválasztva a listából, ha igen, menti az új értéket, és kivált egy onChange eseményt. Értékek mentésére az előzőekben bemutatott módszert használtam.
52
var settings = new Settings(system.widgetDataFolder + "/settings.xml"); function Selector(name, description) { this.property = settings.createGroup(name); this.description; this.onChange; this.getValue = function() { return this.property.get("option"); } this.setValue = function(new Value) { var oldValue = this.getValue(); var retVal = oldValue != new Value; if (retVal) this.property.set("option", newValue); return retVal; } this.show = function(options, optionValues) { var formFields = new Array(); formFields[0] = new FormField(); formFields[0].name = "popname"; formFields[0].title = "Options: "; formFields[0].type = "popup"; formFields[0].option = options; formFields[0].optionValue = optionValues; formFields[0].defaultValue = this.getValue(); formFields[0].description = this.description; var formResult = form(formFields, "Selector", "OK", "Cancel"); if (formResult != null && this.setValue(formResult[0])) { if (this.onChange != null) { this.onChange(); } } } }
45. forrás. A Selector osztály forrása A fenti kódból jól látható, hogy minden listához csak egy tulajdonság tartozik, amelyet most option-nak nevezek. Egy példány show metódusában átadjuk a mindig frissített listát a hálózati interfészekről, vagy lemezekről. Látható, hogy a lehetőségek listája (options) mellett egy optionValues listát is át kell itt adni. Ennek oka, hogy interfészek monitorozásakor lehet például két azonos nevű interfész. Ekkor az interfész azonosítása nem a neve alapján, hanem a hardveres címe alapján történik. Ilyenkor az options tömb az interfészek neveit, míg az optionValues (mely a felhasználó számára láthatatlan) az interfészek címeit tartalmazza. Mint a korábbiakban már ismertettem, az egyedi beállításokat a mini-alkalmazás bezárásakor menteni kell (settings.save). Ehhez például a widget objektum onUnload eseményét lehet használni. A Selector osztály osztálydiagramját a következő ábra (16. ábra) szemlélteti:
53
16. ábra. A Selector osztály 4.7.7. Hardverenkénti monitorozás Egy fontos újítás a hardverenkénti monitorozás. Ehhez egyfelől szükség volt az előzőekben már bemutatott Selector osztályra, másfelől egy hatékony módszert kellett találnom, mely az információk frissítésekor mind a válaszható hardverek listáját, mind a kiválasztott hardver adatait (grafikonhoz) naprakészen tartja. Mivel mind a hálózati interfészeknél, mind a helyi lemezeknél ez hasonlóan zajlik, ezért csak az általános módszert mutatom be (a későbbiekben csak a megfelelő változóneveket kell majd behelyettesíteni). Mint ismeretes, mindkét esetben a COM kiszolgáló biztosítja az adatokat, egy-egy listán keresztül (a listák egyes elemei reprezentálják a hálózati interfészeket, vagy lemezeket). Tegyük fel, hogy a listából már kiválasztottuk a monitorozni kívánt eszközt (a mikéntjéről később). Ekkor az aktivitást az eszköznél két mérés eredménye közti különbséggel azonosíthatjuk (régi mérési eredményből kivonjuk az új mérési eredményt, ez után az új mérési eredmény veszi át a régi mérési eredmény szerepét, majd megismétlődhet a mérés): var ujMeresiEredmeny; var regiMeresiEredmeny = 0;
//uj eszkoz valasztasakor mindig nullazodik
//itt megszerezzuk az uj meresi eredmenyt function frissits() { if (!ujMeresiEredmeny) { alert('Hiba az eszköz tulajdonságainak megszerzése közben'); return; } if (regiMeresiEredmeny) { //az elso hivaskor meg nem lehet aktivitast szamolni alert('Az aktivitás: ' + (ujMeresiEredmeny - regiMeresiEredmeny)); } regiMeresiEredmeny = ujMeresiERedmeny; }
46. forrás. Aktivitás kiszámítása Ha a megfelelő eszközt nem találtuk meg, vagy hiba történt a tulajdonság megszerzése között, akkor az új merési eredmény 0 lesz, ezzel jelezve a hibát. Sikeres lekérdezés esetén az aktuális aktivitást az előzőekben már bemutatott grafikonon is ábrázolni kell. Ehhez az aktivitást valahogyan százalékos alakra kellett hozni. Mivel az aktivitást mind a hálózati interfészeknél, mind a lemezeknél bájtokban kapjuk meg, ezért definiálhatunk (vagy beállíthat a felhasználó) egy maximum 54
sávszélességet, mely felett a grafikon mindig 100%-ot fog mutatni. Ez után már egyszerűen százalékos alakra hozhattam: 100 · aktivitas / maximumSavszelesseg. Teljes részletességében nem vizsgálom az eljárást, mivel az az eredeti mini-alkalmazásból lett átvéve, és újításokat nem tartalmaz. Most, hogy láttuk, miként számolhatunk aktivitást, rátérhetünk a tulajdonságértékek (mérési eredmények) megszerzésének mikéntjére. Az alapötlet az, hogy a felhasználó által kiválasztott eszköz azonosítóját egy változóba mentjük (selectedDevice). Ezt követően frissítéskor bejárjuk az eszközöket reprezentáló listát. A lista minden egyes elemének nevét és egyedi azonosítóját egy-egy előzőleg kinullázott tömbbe (devicenames, ids) mentjük. Mindkét tömb az előzőekben bemutatott Selector osztály egy példányának show metódusához kell, mely metódus létrehozza az eszközválasztó ablakot. Amennyiben bejárás közben az egyik eszköz egyedi azonosítója megegyezik a selectedDevice értékével, úgy annak aktivitás tulajdonságait a megfelelő változókba mentjük: activity = 0; //megtalaltuk az eszkozt? ids.length = 0; deviceNames.length = 0;
//egyedi azonosito //eszkoznevek, melyek kozul a felhasznalo valaszthat
selectedDevice = deviceSelector.getValue(); try { var devices = simpleMonitor.xyz;
//Selector egy peldanya
//megfelelo tulajdonsagnev: interfaces vagy //diskDrives
for (i = 0; i < devices.count; i++) { //bejaras var device = devices.item(i); //aktualis eszkoz var id = device.id; //physAddr vagy rootDirectory var name = device.name; //eszkoz neve ids.push(id); deviceNames.push(name); if (id == selectedInterface) { activity = device.activity; } } } catch (e) { //kivetelkezeles }
47. forrás. Adott eszközhöz tartozó információk frissítése 4.7.8. Teleptöltöttség mérés A mini-alkalmazás újításai között szerepel a teleptöltöttség mérés is. Ehhez a Widget Engine rendelkezésünkre bocsájtja a battery tömböt, mely a rendszerben megtalálható telepeket reprezentálja, minden egyes telephez egy-egy Battery objektumot rendelve. A tömb hossza (elemszáma) Windows rendszerek alatt mindig 1 vagy 0 (többtelepes rendszerek esetén a tömb egyetlen eleme reprezentálja az összes akkumulátort).
55
17. ábra. A Battery osztály Most nem foglalkozom a Battery objektum [1] összes tulajdonságával, csak a minialkalmazásban használtakkal. 1. isPresent: A tulajdonság értéke igaz, ha a telep fizikailag jelen van a rendszerben. 2. powerSourceState: A tulajdonságnak két szöveges értéke lehet, a „AC Power” vagy a „Battery Power”, attól függően, hogy a rendszer telepről, vagy hálózati áramról működik. 3. maximumCapacity: Mivel a töltöttség mérése (most már) százalékosan történik, ezért ennek a tulajdonságnak az értéke mindig 100. 4. currentCapacity: Az aktuális töltöttség százalékokban. 5. timeToEmpty: Mennyi idő van még a telep lemerüléséig (másodpercekben). Ha a tulajdonság értéke -1, akkor a rendszer még számolja a fennmaradó időt. 6. timeToFullCharge: Hasonló az előzőekben tárgyalt tulajdonsághoz, csak a teljes töltöttséghez hátralévő időt jelzi. Az aktuális töltöttséget round((currentCapacity / maximumCapacity) · 100) módon érdemes számolni, ez a régebbi rendszerek esetén (ahol a currentCapacity és maximumCapacity tulajdonságok még nem százalékosan voltak értelmezendők) is működik. Továbbá a felhasználó számára érdemesebb a fennmaradó időket (a telep lemerüléséig vagy feltöltéséig) nem másodpercekben mutatni, hanem például órákban és percekben. Ehhez egy saját függvényt készítettem: function minutesToHours(theSeconds){ var fullMinutes = String(theSeconds % 60); if (fullMinutes.length == 1) fullMinutes = "0" + fullMinutes; if (theSeconds > 60) fullHours = String(((theSeconds - fullMinutes)/60) % 60); else fullHours = "0"; return fullHours + ":" + fullMinutes; }
48. forrás. Másodpercek konvertálása olvashatóbb formátumra Ezután már elkészíthettem a méréshez az algoritmust is. Ehhez a fentebb leírtakon felül érdemes még megjegyezni, hogy ha a battery tömb érvénytelen indexű eleméhez szeretnénk hozzáférni, akkor az érték null variant lesz (ezzel az információval megspórolhatjuk, hogy használnunk kelljen a system objektum batteryCount
56
tulajdonságát). Ezek után a wifi méréséhez hasonlóan (4.7.9. fejezet), a megfelelő töltöttségi szintekhez a megfelelő képet kell használnunk: function updateBatteryInfo() { var battery = system.battery[0]; var src; var tooltip; if (battery && battery.isPresent) { if (battery.powerSourceState != "AC Power") { var maximumCapacity = battery.maximumCapacity; var currentCapacity = battery.currentCapacity; var capacity = Math.round((currentCapacity / maximumCapacity) * 100); if (capacity <= 20) src = "resources/images/bat1.png"; else if (capacity <= 40) src = "resources/images/bat2.png"; else if (capacity <= 60) src = "resources/images/bat3.png"; else if (capacity <= 80) src = "resources/images/bat4.png"; else src = "resources/images/bat5.png"; if (battery.timeToEmpty == "-1") tooltip = "Capacity is " + capacity + "%\nCalculating Time Remaining"; else tooltip = "Capacity is " + capacity + "%\n" + minutesToHours(battery.timeToEmpty) + " Remaining"; } else { src = "resources/images/bat6.png"; if (battery.timeToFullCharge == "-1") tooltip = "Calculating Time Until Full"; else tooltip = minutesToHours(battery.timeToFullCharge) + " Until Full"; } } else { src = "resources/images/bat0.png"; tooltip = "No Battery"; } batteryImage.src = src; batteryImage.tooltip = tooltip; }
49. forrás. Teleptöltöttség mérése a mini-alkalmazásban 4.7.9. Vezeték nélküli hálózatok A widget utolsó újítása a vezeték nélküli hálózatok monitorozása, melyhez a back-end részben már kitárgyalt COM objektumokat használja. Ezen objektumokat még egyszer nem mutatnám be, ehelyett a felhasználó felé prezentált információk megjelenítését veszem jobban szemügyre: Frissítéskor három különböző esetet érdemes megkülönböztetni: • Az első esetben nincs vezeték nélküli hálózati interfész telepítve a rendszerben. Ezt két módon állapíthatjuk meg: - vagy az available tulajdonsággal - vagy az interface tulajdonság érvényességének vizsgálatával Ekkor a főablakon egy olyan képet jeleníthetünk meg, mely mutatja a fenti esetet a felhasználónak (jelen esetben ez a WiFi ikon lesz áthúzva, de lehet akármilyen beszédes kép is). • A második eset, hogy található interfész a rendszerben, ám az nincs kapcsolódva hálózathoz (ehhez az interfész connected tulajdonságát kell 57
•
megvizsgálni). Ekkor ismét beállítható az előző ikon azzal a különbséggel, hogy egy tooltip-be már felvehetjük az interfész nevét. Az utolsó lehetőség, hogy található interfész a rendszerben, és kapcsolódva is van hálózathoz. Ekkor a kapcsolati jel erősségének megfelelően beállíthatjuk a megfelelő „indikátor” képet, majd megvizsgálhatjuk a kapcsolat biztonságosságát is (secured tulajdonság). Biztonságos kapcsolat esetén újabb tooltip-pet vagy ikont állíthatunk be.
A fentieket mutatja be az alábbi kód: function updateWirelessInfo() { var src; var src2; var tooltip; try { var interface = simpleMonitor.w ireless.interface; if (interface && interface.connected) { var description = interface.description; var connection = interface.connection; var signal = connection.signal; if (signal <= 20) src = "resources/images/net1.png"; else if (signal <= 40) src = "resources/images/net2.png"; else if (signal <= 60) src = "resources/images/net3.png"; else if (signal <= 80) src = "resources/images/net4.png"; else src = "resources/images/net5.png"; var netw ork = connection.network; var secured = connection.secured; tooltip = description + '\nConnected To ' + network + '\nSignal Strength: ' + signal + '%' + '\nSecurity Enabled: ' + secured; src2 = secured ? "resources/images/lock.png" : ""; }else if (interface) { var description = interface.description; src = "resources/images/net0.png"; src2 = ""; tooltip = description + "\nNo Connection"; } else { src = "resources/images/net0.png"; src2 = ""; tooltip = "No WiFi"; } } catch (e) { alert("Failed to update wireless info: " + e); closeW idget(); } w ifiImage.src = src; w ifiImage.tooltip = tooltip; lockImage.src = src2; }
50. forrás. WiFi monitorozása a mini-alkalmazásban 58
Érdemes megjegyezni, hogy a főablak definiálásakor a wifiImage és lockImage objektumokat már létrehoztam, valamint beállítottam a helyüket, viszont src tulajdonságukat üresen hagytam (emiatt nem jelent meg semmilyen kép). Továbbá, hogy akár csak a processzorinformációk megszerzésekor, itt is érdemes a fenti függvényt egyszer az inicializációs szekcióban is lefuttatni, így a WiFi monitorozáshoz tartozó timer objektum onTimerFired eseményének első bekövetkezése előtt is már meg fog jelenni a megfelelő kép a form-on.
59
5.
ÖSSZEFOGLALÁS
Összességében elmondhatom, hogy a projekt egy igazán izgalmas feladat volt. Ha másra nem is, de gondoljunk csak a benne felhasználásra került technológiákra és programozási nyelvekre: Kezdve a Delphi és JavaScript nyelvek használatától, a COM és XML világán át, egész a mini-alkalmazások DOM modelljének alkalmazásáig, sok mindent felölelt a feladat (hogy akkor a Windows API programozásról már ne is beszéljek). Épp a fentebb bemutatott sokszínűség miatt is választottam ezt a témát.
5.1. Felhasználási lehetőségek Mint azt már a bevezetőben is említettem, a mini-alkalmazást a mindennapi számítógép használat során (szerintem) jól lelhet alkalmazni. Mióta az első futtatható verzió elkészült, én magam is számos alkalommal vettem hasznát, mikor láttam, hogy egy-egy program indulásakor hirtelen mekkora memória vagy merevlemez használat lép fel. Ismerősök visszajelzése alapján a WiFi monitor és teleptöltöttség mérő is hasznosnak bizonyult (persze ezek a visszajelzések nyilván nem tudják helyettesíteni a majdani publikálás utáni visszajelzéseket). Összességében szerintem hasznos, hogy minden egy helyen van. Gondoljunk csak bele: Windows operációs rendszer esetén a mini-alkalmazás által megvalósított funkciók megtekintéséhez legalább három segédprogramot kell elindítanunk (a rendszererőforrás monitort, a vezeték nélküli hálózatok segédprogramot, valamint az energiagazdálkodási lehetőségek vezérlőpultbeli elemet).
5.2. Továbbfejlesztés lehetőségei A továbbfejlesztés lehetőségei közt megemlíthető két rövid és egy hosszú távú cél is. Rövidtávú cél, hogy a galériába történő felvétel után a felhasználói visszajelzések alapján az esetleges hibákat javítsam. Mivel eddig csak szűk kör tudta tesztelni az alkalmazást (pár ismerősömnek küldtem el kiértékelésre), ezért az jelenleg még teszt stádiumban van (erre utal az is, hogy a verziószám mind a háttér, mind az előtér alkalmazásban nem érte el még az 1.0-t). További rövidtávú terv még a motor flat-file formátumának használata. Mint azt már a korábbiakban is említettem, egy új widgetnek a galériába történő felvétele előtt át kell esnie egy biztonsági vizsgálaton is, hogy nem tartalmaz-e kártékony kódokat. Ez a gyakorlatban úgy történik, hogy az elkészült mini-alkalmazást egy erre a célra létrehozott weboldalra kell feltölteni. A pár napos ellenőrzést követően a Yahoo csapata elektronikus formában (e-mail) veszi fel a fejlesztővel a kapcsolatot az esetleges észrevételekkel kapcsolatban. Az én esetemben ezen észrevételek között volt a figyelmeztetés a flat-file formátum használatára, valamint a bináris állományok mennyiségének redukálására. Hosszú távú cél a mini-alkalmazás Gadget Platform-ra történő konvertálása. Mint ismeretes, a Windows Vista rendszereknél bemutatkozott platform szintén minialkalmazások megjelenítésére szolgál. A Microsoft is létrehozta saját „kütyü” galériáját, 60
hasonlóan a Yahoo-éhoz. A mini-alkalmazás még szélesebb körű elterjesztéséhez érdemes azon felhasználókra is gondolni, akik inkább a Windows-al szállított platformot preferálják. Ennek tükrében célszerű lenne a widget-et e környezetre is interpretálni.
Kelt: Pécs, 20
..................................... Solti Dénes
61
IRODALOMJEGYZÉK 1. YAHOO: Konfabulator 4.5 Reference Manual, 2010. http://manual.widgets.yahoo.com/ 2. RAMA KRISHNA VAVILALA: RegSvrEx - An Enchanced COM Server Registration Utility, 2003. http://www.codeproject.com/KB/winsdk/regsvrex.aspx 3. EMBARCADERO: VCL/RTL Reference, 2010. http://docwiki.embarcadero.com/VCL/en/Main_Page 4. MICROSOFT: Automation Programming Reference, 2010. http://msdn.microsoft.com/en-us/library/ms221375.aspx 5. MICROSOFT: WMI Reference, 2010. http://msdn.microsoft.com/enus/library/aa394572(v=VS.85).aspx 6. MICROSOFT: System Information Reference, 2010. http://msdn.microsoft.com/en-us/library/ms724956(v=VS.85).aspx 7. MICROSOFT: IP Helper Reference, 2010. http://msdn.microsoft.com/enus/library/aa366072(v=VS.85).aspx 8. MICROSOFT: File Management Reference, 2010. http://msdn.microsoft.com/en-us/library/aa364233(v=VS.85).aspx 9. MICROSOFT: Device Management Reference, 2010. http://msdn.microsoft.com/en-us/library/aa363239(v=VS.85).aspx 10. MICROSOFT: Volume Management Reference, 2010. http://msdn.microsoft.com/en-us/library/aa365731(v=VS.85).aspx 11. MICROSOFT: Native Wifi Reference, 2010. http://msdn.microsoft.com/enus/library/ms706275(v=VS.85).aspx 12. MICROSOFT: Time Reference, 2010. http://msdn.microsoft.com/enus/library/ms725479(v=VS.85).aspx 13. W3SCHOOLS: XML DOM Tutorial. http://www.w3schools.com/dom/default.asp 14. W3SCHOOLS: XPath Tutorial. http://www.w3schools.com/xpath/default.asp 15. RAY LISCHNER: Delphi kézikönyv. Kossuth kiadó, 2001.
62
FÜGGELÉK I.
Az alábbiakban bemutatásra kerül néhány felhasználói visszajelzés (eredeti nyelven), mely alapján az eredeti mini-alkalmazás módosítására került: Hozzászólás címe
Hozzászóló felhasználóneve
Need customization
Neil
Nice widget. 1) I wish I was able to toggle display between the four options. I really don't need to see the display for data in/out.
Got it working finally! Robin OK so I guessed that it must be a privileges issue in Vista as it tries to access everything which Vista would prevent...So I closed yahoo widgets and started again but this time as administrator ("Run as Administrator" option after right clicking on the shortcut) and it worked like a charm! Feature request
zazaz
Only thing I feel missing is a WiFi signal monitor at the bottom. Maybe being optional if even possible. I would like to remove the IP and have the signal instead for example.
Suggestion
o1sowise
Would it be possible to select the various items for inclusion and exclusion?
eh... not terrible....
clerks
it looks ok, but it only shows the CPU usage on one processor in a dual core PC and it can't be changed.
o1sowise 63
Would it be possible to make the 5 items in the widget selectable, via preferences? I would like to disable the 3 network monitors, but keep the others (and maybe even flip those on and off depending on my mood or need)?
1 thing would make this a complete package in my opinion, I'm sure some 1 has commented on this alread, I apologize for being to lazy to read them right now :): Current hard drive reading / writing in B/s or b/s whatever. Rod Network I/O sums DSL and Ethernet card throughputs...how can I measure only one? 2. táblázat. Felhasználói visszajelzések, eredeti nyelven
64
II.
A back-end réteg fejlesztése során érdemesnek tartottam egy tesztalkalmazás létrehozását is. Ennek gyakorlatban úgy vettem hasznát, hogy először a kiszolgálót fejlesztettem, majd e segédalkalmazás segítségével teszteltem azt (nem dob-e a kiszolgáló kivételt, stb.), így a front-end rész írásakor már nem kellett attól tartanom, hogy az esetleges hibákat a kiszolgáló generálta. Az alábbiakban a tesztalkalmazás főablakát láthatjuk. Észrevehetjük, hogy az egyes gombok a SimpleMonitor.Information osztály egy példányának egyes tulajdonságait reprezentálják. Listatulajdonság (cpus, diskdrives, interfaces) esetén a lista minden elemének (tipikusan újabb objektumok) minden tulajdonsága kiírásra kerül (az ábrán például a processzorokat reprezentáló lista lett kiírva), míg nem listatulajdonság esetén a megfelelő objektum (jelen verzióban egy ilyen tulajdonság van, a wireless) összes tulajdonsága, vagy pedig az adott tulajdonság (uptime, localip) értéke kerül kiírásra.
18. ábra. A TestSM alkalmazás A dolgozathoz mellékelt CD-n mind a tesztalkalmazás bináris állománya, mind annak forrása megtalálható.