Продолжаю переводить руководство по D-Bus для рубистов (и не только). Всё добро хранится в Git (git clone git@gitorious.org:ruby-dbus-doc-russian/mainline.git) и ждет патчей от знатоков русского языка (коим я не являюсь).
Эта глава описывает создание проcтого D-Bus клиента c помощью Ruby и включает следующие темы:
Если вы хотите использовать библиотеку, вы должны включить её в вашу программу с помощью:
require 'dbus'
Это всё! Теперь мы можем перейти к использованию…
Обычно в системе запущено две шины - шина сессии и системная шина. Доступ к системной шине может быть получен следующим образом:
bus = DBus::SystemBus.instance
Вероятно вы уже догадались каким образом получить доступ к шине сессии:
bus = DBus::SessionBus.instance
Разрешите мне привести пример с использованием шины сессии. Предположим я хочу получить доступ к объекту какого-либо клиента через шину сессии. Этот конкретный D-Bus клиент предоставляет сервис с именем org.gnome.Rhythmbox.
rb_service = bus.service("org.gnome.Rhythmbox")
В этом примере я получаю доступ к сервису org.gnome.Rhythmbox, который в свою очередь предоставлен приложением Rhythmbox.
Отлично, сейчас у меня есть манипулятор (handler) для сервиса и я знаю что он экспортирует объект “/org/gnome/Rhythmbox/Player”. Получить доступ к этому объекту можно с помощью метода object:
rb_player = rb_service.object("/org/gnome/Rhythmbox/Player")
Отлично, это было просто. Давайте предположим что этот конкретный объект интроспектабелен. Чаще всего так и есть. Объект @rbplayer@, который мы получили, является манипулятором (handler) для удаленного объекта. Обычно такие объекты называютсяпрокси объектами. Было бы хорошо если бы они имели методы, и эти их методы посылали вызовы через D-Bus, которые бы вызывали реальные методы в удаленном объекте (другом процессе). Обнаружить методы дляинтроспектабельных_ объектов достаточно просто:
rb_player.introspect
Обратите внимание, что не все сервисы или объекты интроспектабельны, поэтому вы иногда должны делать это вручную. Позвольте напомнить, что объекты D-Bus имеют интерфейсы а интерфейсы в свою очередь имеют методы. Давайте сейчас получим доступ к этим методам:
rb_player_iface = rb_player["org.gnome.Rhythmbox.Player"]
puts rb_player_iface.getPlayingUri
Вы должны получить правильный интерфейс перед тем как вызвать метод какого-либо объекта. Это немного утомительно, и проще использовать сокращение, которое устанавливает интерфейс для сервиса по умолчанию:
rb_player.default_iface = "org.gnome.Rhythmbox.Player"
puts rb_player.getPlayingUri
Вызов defaultiface=_ указывает интерфейс, который будет использован по умолчанию когда вы напрямую вызываете методы, отсутствующие у прокси-объекта не указывая конкретный интерфейс.
Шина сама по себе имеет соответствующий ей интроспектабельный объект. Вы можете получить к нему доступ через метод bus.proxy. Например вы можете получить массив имен доступных сервисов:
bus.proxy.ListNames[0]
D-Bus асинхронен. Это значит вы не должны ждать до тех пор пока придет ответ на отправленное сообщение. Когда вы вызываете удаленный метод, выполнение которого может занять много времени, вы не хотите чтобы ваше приложение повисло в ожидании ответа. Асинхронность существует для решения этой проблемы. А что если вы не хотите ждать занчание-ответ от удаленного метода но всё-же хотите предпринять какие-нибудь действия когда оно придет?
Есть классический метод программирования таких событийных механизмов. Вы производите какой-нибудь рассчет, вызываете метод и одновременно определяете обработчик, который сработает когда вы получите ответ. В таком случае вы запускаете главный цикл, который ответственнен за правильный запуск обработчиков. Вот как это можно сделать:
rb_player.getPlayingUri do |resp|
puts "URI трэка #{resp}"
end
puts "Смотри, я не жду ответа!"
loop = DBus::Main.new
loop << bus
loop.run
Этот код напечатает следующее:
Смотри, я не жду ответа!
URI трэка file:///path/to/file.ogg
(вы ведь жмёте свою музыку в OGG Vorbis, правда?)
Сигналы - это вызовы посланные вашему приложению из удаленных объектов. Вы конфигурируете приложение на прием и обработку сигнала специальным обратным вызовом (callback). Затем запущенный главный цикл сам настраивается таким образом, чтобы получить сигнал и обработать его обратным вызовом. Вы можете зарегистрировать обработчик сигналов таким образом:
player.on_signal("elapsedChanged") do |event|
puts event
end
Есть несколько способов для интроспекции удалённых сервисов. Вы можете просто вызвать метод Introspect() и прочитать XML-вывод. Однако в этом учебнике я предполагаю что вы захотите сделать это с помощью API предоставляемым Ruby D-Bus.
Вы можете получить результат интроспекции от сервиса а не только от объектов.
rb_service = bus.service("org.gnome.Rhythmbox")
rb_service.introspect
p rb_service.root
Эта последовательность выведит древообразную структуру, которая представляет пути к объектам. В нашем случае вывод таков:
</: {org => {gnome => {Rhythmbox => {Player => ..fdbe625de {},
Shell => ..fdbe6852e {},PlaylistManager => ..fdbe4e340 {}}>
Читайте с лева на право: корневой узел это ”/”, он имеет дочерний узел “org”, “org” имеет один дочерний узел “gnome”, и “gnome” имеет один дочерний узел “Rhythmbox”. Rhythmbox имеет дочерние древообразные узлы “Player”, “Shell” и “PlaylistManager”. Эти три последних дочериних узла имеют странные числа, которые говорят нам о том, что они содержат в себе экземпляры объектов. Эти экземпляры объектов уже интроспектированы.
Возможно вам поможет диаграма, если моя проза была не совсем ясна:
/
org
gnome
Rhythmbox
Shell (with object)
Player (with object)
PlaylistManager (with object)
Вы можете иметь объект в любом узле, т.е. вы не ограничены верхним уровнем дерева. Вы можете получить доступ к любому узлу:
rb_player = rb_service.root["org"]["gnome"]["Rhythmbox"]["Player"]
rb_player = rb_service.object("/org/gnome/Rhythmbox/Player")
Разница между этими вариантами заключается в том, что для первого случая rb_service должен быть интроспектирован заранее. Так-же в первом случае полученный объект rb_player уже интроспектирован в то время как во втором - нет.