Как стать автором
Поиск
Написать публикацию
Обновить

«Freelance watchdog» — дружим Perl и D-Bus

Здравствуйте! Я думаю многих фрилансеров, как и меня, заботит вопрос как не пропустить интересный проект и при этом не проводить часы например на сайте free-lance.ru обновляя список проектов и ожидая того самого, который заинтересует. В какой-то момент мне надоело тратить время впустую и я решил раз и навсегда покончить с подобным безделием. Быстро сделал простой парсер и добавил его вызов в Cron с периодичностью в минуту я принялся экспериментировать с наглядным выводом результатов. Перепробовав несколько вариантов информирования себя я узнал что утилита wall рассылающая сообщения на все открытые в данный момент консоли загадочным образом искажает русский текст в utf8, а любимый osd_cat отказывается выводить сообщения если его вызывать из скрипта запускаемого через cron, я остановился на выводе сообщений через KNotify, стандартную службу системных уведомлений в KDE использующую D-Bus — интерфейс межпроцессного взаимодействия являющийся частью проекта freedesktop.org, подробнее о котором можно прочитать тут: Статья на Википедии, Страница проекта на freedesktop.org. В данной статье я хочу остановиться именно на работе с D-Bus, так как мне кажется данная тема на хабре вообще ещё не раскрывалась.
Достаточно быстро я нашёл в cpan модули, которые предоставляют интерфейс для работы с D-Bus, их оказалось достаточно много. Из всего разнообразия мне понадобилось два: Net::DBus который предоставляет доступ к базовым функциям и Net::DBus::Reactor, который позволяет повесить обработчик на события возвращаемые от KNotify. Для работы с D-Bus сначала необходимо определить адрес сокета, через который происходит общение c сервисом. Он присутствует в переменных окружения программ запускаемых из KDE, однако при старте скрипта из Cron оказалось что данная переменная отсутствует. Вот эта переменная:

$ echo $DBUS_SESSION_BUS_ADDRESS
unix:abstract=/tmp/dbus-90YGBbKyPJ,guid=d4f4494988f1826aea58b612000000bd

Узнать её значение можно как минимум из двух мест, во первых она прописана в файле содержащем переменные окружения любого процесса, использующего D-Bus, например того-же самого KNotify:

$ ps aux | grep knotify
1000 13742 0.0 2.4 149464 50716 ? Sl Apr09 0:01 /usr/bin/knotify4
1000 16306 0.0 0.0 5228 708 pts/4 S+ 01:27 0:00 grep --colour=auto knotify
$ grep -z DBUS_SESSION_BUS_ADDRESS /proc/13742/environ
DBUS_SESSION_BUS_ADDRESS=unix:abstract=/tmp/dbus-90YGBbKyPJ,guid=d4f4494988f1826aea58b612000000bd

Ключ -z в данном случае нужен так как в файле environ записи разделены не символом переноса строки а ноль-байтом.
Во вторых то-же самое значение можно увидеть в файле сессии самого сервиса D-Bus, который лежит в директории .dbus/session-bus/ находящейся в домашней папке пользователя, из-под которого выполнен вход в KDE:

$ cat ~/.dbus/session-bus/e0828276ff34f7799cd0f6670000605a-0
# This file allows processes on the machine with id e0828276ff34f7799cd0f6670000605a using
# display :0 to find the D-Bus session bus with the below address.
# If the DBUS_SESSION_BUS_ADDRESS environment variable is set, it will
# be used rather than this file.
# See "man dbus-launch" for more details.
DBUS_SESSION_BUS_ADDRESS=unix:abstract=/tmp/dbus-90YGBbKyPJ,guid=d4f4494988f1826aea58b612000000bd
DBUS_SESSION_BUS_PID=13601
DBUS_SESSION_BUS_WINDOWID=4194305

Так как выполнять как минимум две системных команды для поиска адреса мне показалось много я решил обойтись одной командой, пойти вторым путём и читать это значение из файла:

if($ENV{DBUS_SESSION_BUS_ADDRESS} eq ""){
my $dbus = `grep "^DBUS_SESSION_BUS_ADDRESS" $homedir.dbus/session-bus/*`;
$dbus =~m/DBUS_SESSION_BUS_ADDRESS=(.*)/;
$ENV{DBUS_SESSION_BUS_ADDRESS}=$1;
}

Далее необходимо подключиться к шине к отослать сообщение KNotify, которая должна отобразить его во всплывающем окне. К сожалению найти приверы общения с KNotify мне не удалось, поэтому чтобы определиться в каком виде и куда (с точки зрения D-Bus) отсылать сообщения, я воспользовался утилитой dbus-monitor, которая выводит на консоль все сообщения передаваемые по шине D-Bus, а также программой qdbusviewer, которая позволяет отсылать сообщения и видеть ответы на них. Итак, по порядку. Запускаю dbus-monitor и жду пока кто-то из моих контактов icq пришлёт мне сообщение, в этот момент KNotify отображает окошко, а dbus-monitor выдаёт лог обмена сообщениями между Kopete и KNotify, из которого меня заинтересовало следующее (Привожу сразу со своими #комментариями что для чего нужно):

...
method call sender=:1.33 -> dest=org.kde.knotify serial=1994 path=/Notify; interface=org.kde.KNotify; member=event #Название сервиса, объекта и метода, описано ниже
string "kopete_contact_incoming" #тип события
string "kopete" #Название приложения вызывающего метод
array [ #Сдесь передаются данные специфические для Kopete, у меня этот массив пустой
variant string "contact"
variant string "{c661d146-c5bd-4feb-983b-9e5900a66307}"
variant string "group"
variant string "10"
]
string "Incoming message from Zendo" #Заголовок всплывающего окна
string "+" #Текст в окне, можно использовать теги и вставлять картинки через тег
array [ #Для чего этот массив я не знаю
]
array [ #Тут мы указываем название кнопок в окне, если они нужны
string "Просмотреть"
string "Проигнорировать"
]
int32 0 #Это и следующее число у меня равно 0
int64 46689228
...
...
...#Окошко закрылось при нажатии на кнопку
signal sender=:1.15 -> dest=(null destination) serial=1203 path=/org/freedesktop/Notifications; interface=org.freedesktop.Notifications; member=ActionInvoked
uint32 99
string "1"
signal sender=:1.13 -> dest=(null destination) serial=1473 path=/Notify; interface=org.kde.KNotify; member=notificationActivated
#Ответ от KNotify При нажатии на кнопку
int32 1015
int32 1 #Номер нажатой кнопки
...
...
... #Окошко закрылось по таймауту
signal sender=:1.13 -> dest=(null destination) serial=1545 path=/Notify; interface=org.kde.KNotify; member=notificationClosed
int32 1075



Итак, я узнал следующие интересующие меня параметры: название сервиса org.kde.knotify, объект непосредственно отвечающий за приём сообщений /Notify, и вызываемый метод этого объекта member=Notify, также получил пример данных передаваемых методу и пример возвращаемых значений при нажатии на кнопку «Просмотреть» в окне KNotify (Последние три строки из лога). Далее используя полученные данные и метод научного тыка я окончательно определил какие поля за что отвечают. В результате появился вот такой скриптик:

#!/usr/bin/perl
use LWP::UserAgent;
use utf8;
use strict;
use Net::DBus;
use Net::DBus::Reactor;
my $wfi = "(?:линукс|админ(?!к)|linux|rh|red\s?hat|centos|ubuntu|postfix|apache)";
my $url = "http://www.free-lance.ru";
my $ua = LWP::UserAgent->new(timeout => 30);
my $response = $ua->request(HTTP::Request->new('GET', $url));
my $code = $response->code;
my $homedir="/home/cherick/";
my $i;
my @match_proj;
my %proj_ignore;
if($ENV{DBUS_SESSION_BUS_ADDRESS} eq ""){
my $dbus = `grep "^DBUS_SESSION_BUS_ADDRESS" $homedir.dbus/session-bus/*`;
$dbus =~m/DBUS_SESSION_BUS_ADDRESS=(.*)/;
$ENV{DBUS_SESSION_BUS_ADDRESS}=$1;
}
if($code == 200){
my $content = $response->decoded_content;
open(IGNORE, "</home/cherick/scripts/fl_watchdog.ignore") or die "can't open fl_watchdog.ignore";
while (){
if(m/^([0-9]+)$/){
$proj_ignore{$1} = $1;
}
}
close IGNORE;

while ($content=~m/<div class="project.*href="\/projects\/\?pid=([0-9]+)">([^<]*$wfi[^<]*)/ig){
if($proj_ignore{$1} ne $1){push @match_proj, {content => $2, pid => $1};}
}
my $proj_count = $#match_proj;
if($proj_count > 0){
my $bus = Net::DBus->session;
my $myservice = $bus->export_service("org.kde.flwd");
my $service = $bus->get_service("org.kde.knotify");
my $object = $service->get_object("/Notify", "org.kde.KNotify");
$object->connect_to_signal('notificationActivated', sub{
my $t1 = shift;
my $t2 = shift;
open(IGNORE, ">>/home/cherick/test_scripts/fl_watchdog.ignore") or die "can't write to fl_watchdog.ignore";
for($i=0; $i<$proj_count; $i++){
print IGNORE "$match_proj[$i]{pid}\n";
}
close IGNORE;
if($t2 eq "1"){
for($i=0; $i<$proj_count; $i++){
qx"firefox www.free-lance.ru/projects/?pid=
$match_proj[$i]{pid}";
}

}
if($t2 eq "2"){
}
exit(0);
});
$object->connect_to_signal('notificationClosed', sub{
exit(0);
});

my $proj_list = '';
for(my $i=0; $i<$proj_count; $i++){
$proj_list .= "$match_proj[$i]{content}<br/><br/>";
}
$object->event("warning", "kde", [], "Новые проекты:","$proj_list", [], ["Просмотреть", "Игнор"], 30*1000, 0);

my $reactor = Net::DBus::Reactor->main();
$reactor->run();
exit(0);
}
}


При появлении интересных проектов выводится окно с двумя кнопками: «Просмотреть» и «Игнор». При нажатии кнопки «Просмотреть» скрипт открывает ссылки на проекты в Фаерфоксе, при нажатии «Игнор» просто выходим из скрипта. Так-же при нажатии на любую из кнопок в файл fl_watchdog.ignore заносятся PID найденых проектов и в следующий раз эти проекты игнорируются.
Также попутно посмотрев интерфейсы Kopete выяснил что в принципе через D-Bus можно отсылать сообщения, менять статусы и делать ещё много чего интересного в Kopete из своего скрипта. А мой Firefox 3.6.13 хоть и собран у меня с флагом dbus почему-то никакого интерфейса не имеет, что для меня остаётся загадкой.
Что можно почитать по теме:
Модули на cpan.org и примеры работы с ними

Пример работы с Pidgin через D-Bus
Вешаем свой скрипт на событие блокировки экрана
Теги:
Хабы:
Данная статья не подлежит комментированию, поскольку её автор ещё не является полноправным участником сообщества. Вы сможете связаться с автором только после того, как он получит приглашение от кого-либо из участников сообщества. До этого момента его username будет скрыт псевдонимом.