Entendendo e Usando D-Bus, parte 1
From CInLUG
Conteúdo |
Introdução
Nesses dias de web 2.0 em que você consegue ter as músicas que escuta enviadas para serviços como o last.fm, essa lista de músicas exibida em seu blog via RSS, que por sua vez é sindicado num planet via Atom. Nomes demais, não? Mas os envolvidos se comunicam muito bem, e é esse o ponto. Então porque essas coisas legais só podem acontecer fora do seu computador?
Entra em cena o D-Bus, um sistema de comunicação entre processos numa mesma máquina. Todo sistema operacional que se preze dispõe de recursos de IPC (inter-process comunication), contudo D-Bus foi criado visando facilitar e estabelecer um padrão para a comunicação entre aplicações do desktop. Diferente de outros métodos de IPC ou das conexões TCP, que transportam dados como fluxos indistintos de bits, D-Bus transporta mensagens contendo dados de tipos bem definidos.
Um uso interessante do D-Bus é um plugin para o Rhythmbox que sempre que uma música começa a tocar, ele atualiza minha mensagem de status no GAJIM para uma frase como "Ouvindo Banda Tal, Música Tal". Isso funciona porque o GAJIM disponibiliza serviços de mudança de mensagem de status na SessionBus, que você saberá o que é se continuar lendo.
Arquitetura
Diferente da comunicação via sockets TCP, que acontece como um fluxo de dados, no D-Bus as aplicações se comunicam através de mensagens contendo dados de tipos bem definidos. Resumidamente o sistema D-Bus consiste da especificação do padrão, implementada na libdbus, e do daemon roteador de mensagens.
Normalmente as aplicações acessam a libdbus via um binding, neste artigo usaremos o binding para python pela praticidade (se você tiver um desktop GNU/Linux moderno não vai precisar instalar nada para este artigo), mas vários outros estão disponíveis.
Como se vê no diagrama acima, as aplicações não "conversam" diretamente, tudo passa pelo daemon roteador, que recebe a mensagem do remetente e repassa para o destinatário, ou destinatários, pois pode haver mais de um deles. Outra coisa sobre D-Bus é que ele é orientado a objetos, uma aplicação disponibiliza seus serviços na bus na forma de objetos que podem ser encontrados e instanciados por outra aplicação. Bem, não exatamente instanciados, a instância do objeto é uma só e fica na aplicação, mas os clientes ao requisitarem o tal objeto recebem do binding/implementação d-bus um objeto proxy representando o objeto remoto. Ao chamar os métodos neste proxy, a libdbus transforma as chamdas em mensagens que trafegam até o objeto real que as processa.
Além do tráfego 1 pra 1 da chamada métodos de objetos na bus, as mensagens também podem fluir de 1 para muitos, através dos sinais. De forma semelhante à orientação a eventos da Glib (base da GTK+), aplicações interessadas se subscrevem nos objetos da bus (ou na própria bus) para receberem mensagens sobre determinados eventos dentre os emitidos pelo objeto. Quando a aplicação sinalizar a ocorrência do tal evento, o daemon roteador encaminhará a mensagem para os "assinantes".
Uma última informação para esta parte. Mais de uma bus pode estar disponível ao mesmo tempo, e num sistema comum costumam ser duas:
- SessionBus: responsável pelo tráfego de mensagens entre as aplicações do usuário, durante uma sessão de uso;
- SystemBus: por onde trafegam mensagens do kernel, sobre hardware (pendrive colocada, bateria fraca, etc.), e outras coisas sérias desse tipo.
Usando
Para pegar um objeto da bus, e efetivamente usá-lo como proxy object o sujeito precisa saber de três coisas:
- bus name
- nome distinto do serviço na bus, parecido com nomes de pacote em Java, nos exemplos usaremos br.org.cinlug.
- object path
- cada serviço pode disponibilizar vários objetos, o caminho para cada objeto é dado como um path num sistema de arquivos unix. Exemplos: /org/freedesktop/Hal/Manager ou /MeuObjeto. A primeira forma é a mais comum, mas não é obrigatória.
- interface
- especificam os métodos que podem ser chamados no objeto e os sinais que ele pode enviar. O objeto pode implementar mais de uma interface, o conjunto dessas interfaces define o tipo do objeto.
Proxy Objects & Interfaces
Vamos praticar um pouco, coisa simples. O Notification Daemon, responsável por aquelas mensagens legais que aparecem quando muda música ou chega mensagem via IM, disponibiliza seus serviços via d-bus. O que vamos fazer é pegar o objeto Notification e mandar ele escrever uma informação qualquer.
As informações necessárias:
- bus name: org.freedesktop.Notifications
- object path: /org/freedesktop/Notifications
- interface: org.freedesktop.Notifications
Abra seu terminal python e coloque isso aqui:
>>> import dbus >>> my_bus = dbus.SessionBus() >>> proxy = my_bus.get_object('org.freedesktop.Notifications', '/org/freedesktop/Notifications') >>> notifier = dbus.Interface(proxy, 'org.freedesktop.Notifications') >>> notifier.Notify('meuscript', 0, '/usr/share/icons/Tango/scalable/devices/harddrive.svg', \ ... 'Harddisk em Chamas!', 'Parece que seu hardisk queimou de vez...', '', {}, 5000)
Depois de importar o módulo d-bus, a primeira coisa é pegar a bus que se pretende usar, nesse caso a SessionBus. Para obter o proxy, chamamos get_object com o bus name do serviço seguido do object path do objeto dentro do serviço. Depois disso criamos outro objeto com o método Interface, passando o proxy e o nome da interface como parâmetros. Como este novo objeto está associado à interface, não precisamos explicitá-la em cada chamada de método, o que seria o caso se usássemos apenas o proxy_object. Algo assim:
>>> proxy.Notify('meuscript', 0, '/usr/share/icons/Tango/scalable/devices/harddrive.svg', \ ... 'Harddisk em Chamas!', 'Parece que seu hardisk queimou de vez...', '', {}, 5000, \ ... dbus_interface='org.freedesktop.Notifications')
A definição do método Notify explica os parâmetros detalhadamente.
Chamadas com valor de retorno
Agora vamos pedir ao HAL (ou Hardware Abstraction Layer)
>>> import dbus >>> bus = dbus.SystemBus() >>> hal_manager = bus.get_object('org.freedesktop.Hal', '/org/freedesktop/Hal/Manager') >>> devices = hal_manager.GetAllDevices(dbus_interface='org.freedesktop.Hal.Manager') >>> for dev in devices: ... print dev
Diversos dispositivos serão listados, um deles é o
/org/freedesktop/Hal/devices/computer
Que também é um object path no serviço do HAL, então podemos pegá-lo pelo nome e usar seus métodos.
>>> computer = bus.get_object('org.freedesktop.Hal', '/org/freedesktop/Hal/devices/computer') >>> print computer.GetAllProperties(dbus_interface='org.freedesktop.Hal.Device') >>> print computer.GetProperty('smbios.bios.vendor')
Note que dessa vez usamos a SystemBus que é onde encontramos o HAL, o que é natural, já que hardware é assunto do sistema inteiro. Com o objeto manager conseguimos uma lista de todos os dispositivos dos sistema, mais tarde pegamos um único dispositivo e lemos uma de suas propriedades. As respostas as essas chamadas vêm tão rápido quanto se elas fossem feitas a objetos locais.
Sinais
Como foi dito, um cliente pode registrar seu interesse em eventos dos objetos numa bus (ou eventos da própria bus), basta localizar o objeto e registrar um método callback que será chamado, de forma análoga à conectar sinais de widgets em GTK+. E assim como na GTK+, precisaremos entrar num main loop para receber os eventos.
#!/usr/bin/python # hardware_monitor.py import dbus import dbus.glib import gobject class HardwareMonitor: def __init__(self): bus = dbus.SystemBus() obj = bus.get_object('org.freedesktop.Hal', '/org/freedesktop/Hal/Manager') hal = dbus.Interface(obj, 'org.freedesktop.Hal.Manager') hal.connect_to_signal('DeviceAdded', self.device_added) hal.connect_to_signal('DeviceRemoved', self.device_removed) def device_added(self, device): print '= dispositivo adicionado =' print device def device_removed(self, device): print '= dispositivo removido =' print device if __name__ == '__main__': hm = HardwareMonitor() mainloop = gobject.MainLoop() mainloop.run()
O "main loop" do qual estamos falando é uma espécie de loop infinito (while True) que a cada iteração realiza tarefas como verificar as entradas do usuário, tratá-las como eventos ou sinais, entregar eventos/sinais às partes interessadas, entre outras coisas.
O main loop é fornecido pelo módulo gobject, mas o módulo dbus.glib também precisa ser importado para que o main loop da GLib seja configurado como o loop usado pelo DBus para entrega dos sinais.
Captura genérica de sinais
Também é possível escutar sinais de maneira genérica usando o método add_signal_receiver em um objeto dbus.Bus.Esse método verifica todos os sinais enviados e caso algum dos sinais tenha as caracterísicas desejadas (definidas pelos argumentos abaixo), a função é chamada.
- handler_function - Um objeto "callable" - Função a ser executada caso as características do sinal lançado combinem com as desejadas.
- signal_name - Nome do sinal. Caso seja None (padrão), aceita todos os sinais.
- dbus_interface - Interface DBus que enviou o sinal. Funciona como signal_name (None aceita todos as interfaces).
- Sender named_service - Nome do serviço que enviou o sinal. Funciona como signal_name (None aceita todos os serviços).
- path - Caminho do objeto DBus que enviou. Funciona como signal_name (None aceita todos os objetos).
Na realidade, uma chamada a connect_signal se resume a uma chamada a add_signal_receiver com os parâmetros do objeto/interface em que foi chamado.
Chamadas assíncronas e tratamento de erros
Algumas vezes as respostas às requisições não podem vir imediatamente, como é o caso de muitas operações envolvendo leitura e escrita de grandes volumes de dados em dispositivos, ou ainda dependência de fatores como velocidade da rede. Para esses casos podemos usar as chamadas assíncronas do D-Bus, basta chamar o método assíncrono passando métodos callback para receber a resposta e eventuais erros.
No código a seguir chamamos novamente o método remoto GetAllDevices de um dos exemplos anteriores, mas passando os parâmetros reply_handler e error_handler. Lembre-se que ao fazer chamadas assíncronas um error handler sempre deve ser fornecido.
#!/usr/bin/python # async_test.py import dbus import dbus.glib import gobject class AsyncTest: def __init__(self): bus = dbus.SystemBus() obj = bus.get_object('org.freedesktop.Hal', '/org/freedesktop/Hal/Manager') hal = dbus.Interface(obj, 'org.freedesktop.Hal.Manager') hal.GetAllDevices(reply_handler=self.get_all_devices, error_handler=self.catch_error) def get_all_devices(self, devices): print 'Resultado de GetAllDevices():' for device in devices: print device def catch_error(self, error): print 'Erro!' print error if __name__ == '__main__': hm = AsyncTest() mainloop = gobject.MainLoop() mainloop.run()
Conclusão
Nesta primeira parte do artigo vimos o que é D-Bus e como usá-lo como consumidores. Na próxima parte veremos como escrever serviços e disponibilizá-los na bus.
Referências
P.S.: Agradecimentos à Maíra que me deixou foto-copiar um tutorial (o do segundo link) que ela tinha acabado de imprimir. Foi bem útil. :)
| Autor: Marcelo Lira |






