ukotvit menu |
V Pythonu je, jak jsme zvyklí, D-BUS velmi kvalitně implementován podle pythoních standardů a zásad. Vše je namapováno na Pythoní objekty a používání D-BUS v Pythonu je přirozené a snadné. D-BUS je v Pythonu dostupný přes modul dbus.
Důležité upozornění, dbus je stále ve vývoji a nemá zatím stabilní API. Na internetu se proto můžete setkat s ukázkami, které nefungují. Ve skutečnosti mi dalo práci přijít na to, jak to teď funguje a musel jsem louskat zdrojové kódy HAL monitoru, abych na to přišel. Zde uvedené ukázky jsou platné pro dbus verze 0.40.0 a jsou odzkoušeny na Auroxu 11 (FC4).
Například zde je ukázka, jak využít službu DBus a získat seznam názvů všech registrovaných služeb. Příklad má dvě varianty. Obě mají společné to, že se nejprve vytvoří připojení k BUS daemonu (buď k systémovému nebo uživatelskému). Následně si vyžádáme proxy objekt služby:
1 import dbus 2 3 SLUZBA = "org.freedesktop.DBus" 4 OBJEKT = "/org/freedesktop/DBus" 5 ROZHRANI = "org.freedesktop.DBus" 6 7 bus = dbus.SessionBus() 8 proxy = bus.get_object(SLUZBA, OBJEKT) 9 10 for name in proxy.ListNames(dbus_interface = ROZHRANI): 11 print name
1 import dbus 2 3 SLUZBA = "org.freedesktop.DBus" 4 OBJEKT = "/org/freedesktop/DBus" 5 ROZHRANI = "org.freedesktop.DBus" 6 7 bus = dbus.SystemBus() 8 proxy = sessBus.get_object(SLUZBA, OBJEKT) 9 slDBus = dbus.Interface(proxy, ROZHRANI) 10 11 for name in slDBus.ListNames(): 12 print name
Varianty se liší v použití proxy objektu a rozhraní. První případ se hodí, když pracujeme se spoustou objektů, ale jen velmi krátce, třeba jen jednou, pak je zbytečné připojovat rozhraní k objektu. Druhý způsob se hodí, když s objektem chceme pracovat dlouhodobě.
Obě varianty mají jednu společnou vlastnost, jsou synchronní. D-BUS umožňuje i asynchronní komunikaci. Synchronní komunikace znamená, že náš program o něco požádá službu a pak čeká, dokud služba neodpoví. U asynchronní komunikace to funguje tak, že program o něco požádá službu a předá ji handler (funkci), který má služba použít, až bude mít výsledek. Mezi tím si náš program může dělat co chce. To je trochu náročnější, ale obvykle lepší řešení, zvláště u GUI aplikací, kde je i drobné prodlení hned viditelné na chvilkovém zamrzání GUI. Takže to samé co jste viděli výše, ale asynchronně:
1 import gobject 2 import dbus 3 4 SLUZBA = "org.freedesktop.DBus" 5 OBJEKT = "/org/freedesktop/DBus" 6 ROZHRANI = "org.freedesktop.DBus" 7 8 def hPrintNames(list): 9 for name in list: 10 print name 11 12 def hPrintError(msg): 13 print msg 14 15 bus = dbus.SessionBus() 16 proxy = bus.get_object(SLUZBA, OBJEKT) 17 slDBus = dbus.Interface(proxy, ROZHRANI) 18 19 slDBus.ListNames(reply_handler=hPrintNames, 20 error_handler=hPrintError) 21 22 print "hned pokračuji dál" 23 gobject.MainLoop().run()
Na řádku 8 a 12 si vytvářím dva handlery. První je pro zachycení odpovědi, druhý pro zachycení případné chyby. Na řádku 19 volám službu, chci po ní seznam názvů registrovaných služeb a předávám jí oba handlery.
Program nebude čekat na odezvu, ale bude pokračovat dál, v našem případě se vypíše kontrolní zpráva a spustí mainloop, tedy čekací smyčku na události. Až služba bude mít připraven seznam, zavolá handler hPrintNames() a jako parametr mu předá onen seznam názvů. Handler pak tyto názvy vypíše v cyklu na obrazovku.
Ona čekací smyčka je z modulu gobject, který je součástí GTK. To znamená, že aby to fungovalo, tak D-BUS také musí využívat GTK a je na něm závislý. To je nežádoucí, protože někteří programátoři mohou preferovat jiný GUI toolkit a také je hloupost používat GUI toolkit u ne-GUI aplikace. Není to vlastnost D-BUS jako takového, ale jeho bindingu v Pythonu. Bylo to použito jako dočasné řešení při implementaci a od dbus modulu 0.41.0 je teoreticky možné (nutné) si zvolit implementaci čekací smyčky. Proto by ukázka měla správně začínat takto.
1 import gobject 2 import dbus 3 4 if getattr(dbus, "version", (0,0,0)) >= (0,41,0): 5 import dbus.glib
Novinka je na řádku 4 a 5, zbytek je stejný s předchozí ukázkou. Znalci GTK vědí, že glib je také modul GTK a z toho si odvodí, že importováním dbus.glib explicitně zapínáme v dbusu podporu pro GTK. Nejsem si jist, ale domnívám se, že podpora pro něco jiného není zatím implementována, ale je to řešení pro budoucnost.
Od asynchronní komunikace není daleko k signálům, které jsou svou podstatou nutně asynchronní. Signály si předvedeme u jiné služby, HAL (Hardware Abstract Layer - abstraktní vrstva hardware), kde pomocí signálů budeme zachycovat události přidání a odebrání zařízení. Při zachycení události vypíšeme ID zařízení, které bylo přidáno nebo odstraněno.
1 import gobject 2 import dbus 3 4 if getattr(dbus, "version", (0,0,0)) >= (0,41,0): 5 import dbus.glib 6 7 SLUZBA = "org.freedesktop.Hal" 8 OBJEKT = "/org/freedesktop/Hal/Manager" 9 ROZHRANI = "org.freedesktop.Hal.Manager" 10 11 def hNoveZarizeni(halId): 12 print "Nové zařízení:", halId 13 14 def hOdebranoZarizeni(halId): 15 print "Odebráno zařízení:", halId 16 17 bus = dbus.SystemBus() 18 proxy = bus.get_object(SLUZBA, OBJEKT) 19 slHal = dbus.Interface(proxy, ROZHRANI) 20 21 slHal.connect_to_signal("DeviceAdded", hNoveZarizeni) 22 slHal.connect_to_signal("DeviceRemoved", hOdebranoZarizeni) 23 24 gobject.MainLoop().run()
Jak je vidět, je to prakticky pořád to stejné. Pouze k předání handleru se používá metoda connect_to_signal(), která jako parametr přebírá název signálu a odkaz na handler.
Nevýhoda tohoto řešení je, že služba poskytující tento signál už musí existovat. To může dělat problémy především u spolupracujících služeb, u kterých by se musely hlídat jejich závislosti a pořadí jejich spouštění. Proto D-BUS umožňuje i zaregistrování žádosti o signál služby, která zatím neexistuje, ale v budoucnu existovat může. V takovém případě se nevytváří proxy objekt služby, protože v době volání ještě nemusí existovat.
17 bus = dbus.SystemBus() 18 19 bus.add_signal_receiver(hNoveZarizeni, 'DeviceAdded', 20 ROZHRANI, SLUZBA, OBJEKT) 21 bus.add_signal_receiver(hOdebranoZarizeni,'DeviceRemoved', 22 ROZHRANI, SLUZBA, OBJEKT) 23 24 gobject.MainLoop().run()
Od D-BUS verze 0.36 a dbus verze (0, 43, 0) je možno ještě přidat další argumenty, které se při vzniku signálu předají handleru. Mohlo by to vypadat pak nějak takto:
19 bus.add_signal_receiver(hNoveZarizeni, 'DeviceAdded', 20 ROZHRANI, SLUZBA, OBJEKT, arg0="zpráva")