Одна из этих плюшек это интеграция плееров поддерживающих интерфейс mpris2 в sound indicator.
Что примечательно, веб-приложения, которые могут вести себя как плеер, так же попадают в этот замечательный список. И всё было бы круто, но эта прелесть, по страному капризу разработчиков не поддерживает никаких горячих клавиш, кроме изменений уровня громкости.
Те, кому не охота читать как всё это устроенно, могут проследовать ближе к концу статьи, или прямиком на github. Остальных прошу за мной.
С чего же можно начать такое предприятие?
Для начала, конечно же, стоит погуглить, что как ни странно никаких вразумительных результатов не даст — пара решений для конкретных плееров, да ссылка на баг о том, что при открытом индикаторе не работают горячие клавиши.
Следующим, очевидным шагом является поиск исходников этого компонета, дабы допилить напильником нехватающий функционал. Как оказалось, на официальном сайте unity есть всё необходимое, что бы достаточно оперативно эти самые исходники заполучить. Для этого надо перейти в раздел «Get involved», раз уж мы решили, что нам придеться быть вовлечеными во всё это. Далее стоит проследовать в раздел «Development», т.к. хотим мы собственно исходники, ну а раз мы хотим допилить стандартный компонент, то наш путь лежит в раздел «Common components». Где мы собственно и найдем наш долгожданный индикатор, хотя именно здесь, он почему то фигурирует под именем «Sound Menu», хотя в остальных частях системы он упоминается исключительно как indicator, ну да ладно.
Запилить, запилить немедленно
Качаем исходники…
bzr branch lp:indicator-sound
Поделие выполнено чуть менее чем полностью на vala. Первым порывом становиться впилить побыстрому установку горячих клавиш, перекомпилять и пользоваться результатами труда праведного. Однако скука, навеяная мыслями, что придеться писать конфиг для этих самых клавиш, выдумывать его формат, разбираться как обрабатывать горячие клавиши глобально, разруливать конфликты с горячими клавишами установленными системой и выдумывать как это записать на vala, остановила меня, и заставила почитать исходники чуть более пристально и последовательно.
Что мы имеем
Все исходники, могущие нас заинтересовать, как и ожидалось, лежат в директории src. Начнем как и водится с main.vala:
[CCode (cheader_filename="libintl.h", type="char *")] extern unowned string bind_textdomain_codeset (string domainname, string codeset); static int main (string[] args) { bind_textdomain_codeset (Config.GETTEXT_PACKAGE, "UTF-8"); Intl.setlocale (LocaleCategory.ALL, ""); Intl.bindtextdomain (Config.GETTEXT_PACKAGE, Config.GNOMELOCALEDIR); Notify.init ("indicator-sound"); var service = new IndicatorSound.Service (); return service.run (); }
Здесь мы увидим, что это не какое то классическое UI приложение, предоставляющее иконку в трее, а некая служба:
var service = new IndicatorSound.Service ();
Раз оно служба, значит, всё необходимое для отрисовки и управления доступно по некоему интерфейсу, заглянем поглубже, а именно в service.vala. Строки вида:
//... void bus_acquired (DBusConnection connection, string name) {//...} //... void name_lost (DBusConnection connection, string name) {//...} //...
несомненно, наводят на мысли что здесь замешана некая шина, а именно D-Bus. Бегло погуглив, можно понять что это вполне себе обьектно-ориентированный интрефейс, который, что приятно, можно дергать из консоли и, более того, мониторить то, как взаимодействуют различные службы. Так же имеются и GUI утилиты, например qdbusviewer, который можно установить коммандой:
sudo apt-get install qdbus-qt5
Далее стоит осмотреть внутренности метода run, который и вызываеться в main:
public int run () { if (this.loop != null) { warning ("service is already running"); return 1; } Bus.own_name (BusType.SESSION, "com.canonical.indicator.sound", BusNameOwnerFlags.NONE, this.bus_acquired, null, this.name_lost); this.loop = new MainLoop (null, false); this.loop.run (); return 0; }
В документации про метод own_name сказанно что то мало вразумительное, но выглядит это всё как регистрация на той самой шине.
Самое время поэксперементировать
Комманда gdbus имеет прекрасный и многообщающий метод introspect:
$ gdbus introspect --session --dest com.canonical.indicator.sound --object-path \ /com/canonical/indicator/sound node /com/canonical/indicator/sound { interface org.freedesktop.DBus.Properties { methods: Get(in s interface_name, in s property_name, out v value); GetAll(in s interface_name, out a{sv} properties); Set(in s interface_name, in s property_name, in v value); signals: PropertiesChanged(s interface_name, a{sv} changed_properties, as invalidated_properties); properties: }; interface org.freedesktop.DBus.Introspectable { methods: Introspect(out s xml_data); signals: properties: }; interface org.freedesktop.DBus.Peer { methods: Ping(); GetMachineId(out s machine_uuid); signals: properties: }; interface org.gtk.Actions { methods: List(out as list); Describe(in s action_name, out (bgav) description); DescribeAll(out a{s(bgav)} descriptions); Activate(in s action_name, in av parameter, in a{sv} platform_data); SetState(in s action_name, in v value, in a{sv} platform_data); signals: Changed(as removals, a{sb} enable_changes, a{sv} state_changes, a{s(bgav)} additions); properties: }; node desktop_greeter { }; node phone { }; node desktop { }; };
Так как мы собираемся управлять этой службой, то, скорее всего, наиболее интересным для нас является интерфейс org.gtk.Actions. Эксперементировать предлагаю с плеером vkcom, хотя, по идее сгодится и любой другой. Давайте запустим примерно такую комманду:
dbus-monitor > monitor.log
И немного повзаимодействуем с нашим плеером, а именно нажмем Play, Pause, Next и Previous.
.... #Явно вызов воспроизведения, если судить по названию действия, значит где то ниже, будут отражены и остальные наши действия method call sender=:1.9 -> dest=:1.19 serial=27912 path=/com/canonical/indicator/sound; interface=org.gtk.Actions; member=Activate string "play.vkcomvkcom.desktop" array [ ] array [ ] ... #И точно: ... #Next method call sender=:1.9 -> dest=:1.19 serial=27918 path=/com/canonical/indicator/sound; interface=org.gtk.Actions; member=Activate string "next.vkcomvkcom.desktop" array [ ] array [ ] ... #Previous method call sender=:1.9 -> dest=:1.19 serial=27918 path=/com/canonical/indicator/sound; interface=org.gtk.Actions; member=Activate string "previous.vkcomvkcom.desktop" array [ ] array [ ] ...
А где же Pause спросите вы? Хм, а нетуть, есть только Play/Pause, зависящий от текущего состояния, которое мы конечно же придумаем как узнать. Предположения относительно org.gtk.Actions полностью себя оправдали, давайте теперь попробуем воспроизвести наши действия через другой интерфейс, нежели тот который мы имользовали при тыкании в индикатор, а именно через консольный:
#Play/Pause gdbus call --session --dest com.canonical.indicator.sound --object-path /com/canonical/indicator/sound \ --method org.gtk.Actions.Activate 'play.vkcomvkcom.desktop' [] {} #Next gdbus call --session --dest com.canonical.indicator.sound --object-path /com/canonical/indicator/sound \ --method org.gtk.Actions.Activate 'next.vkcomvkcom.desktop' [] {} #Previous и т.д и т.п. ...
Полный список действий можно узнать выполнив комманду:
gdbus call --session --dest com.canonical.indicator.sound --object-path /com/canonical/indicator/sound --method org.gtk.Actions.List
Думаю по какому принципу они сформированны вы уже уловили.
Реализация
Круто, работает! Да, действительно в таком виде это уже можно использовать — для какого то конкретного плеера… Но, это не наш метод.
Не знаю как у вас, а у меня, обычно, установленно больше одного плеера, и хотелось бы управлять ими всеми. Для этого придеться придумать какой то механизм для переключения «текущего» плеера, ведь играть могут одновреммено несколько проигрывателей. Для этого, не мешало бы, иметь способ получить полный список проигрывателей, которые управляються indicator sound service. Немного погуглив, легко понять что этот список лежит в некой «dconf databse», манипулировать с которой можно с помощью утилиты dconf. Попробуем?
dconf read /com/canonical/indicator/sound/interested-media-players
Не правда ли не очень читаемо? А так:
dconf read /com/canonical/indicator/sound/interested-media-players | sed -e "s:[],'\[]::g" -e "s:\s:\n:g"
Sed наше все. Ну и что, а как же теперь выбирать «текущий»? Ну, я решил эту проблему так:
#Место где мы будем хранить информацию о текущем плеере UCS_CACHE=~/.cache/unity-control-sound UCS_CURRENT_PLAYER_FILE=$UCS_CACHE/current-player #Список зарегестрированных проигрывателей UCS_INTERESTED_PLAYERS=`dconf read \ /com/canonical/indicator/sound/interested-media-players \ | sed -e"s:[],'\[]::g" ` #Список предпочитаемых проигрывателей UCS_PREFFERED_PLAYERS=`dconf read /com/canonical/indicator/sound/preferred-media-players \ | sed -e "s:[],'\[]::g"` mkdir -p $UCS_CACHE touch $UCS_CURRENT_PLAYER_FILE UCS_CURRENT_PLAYER=`cat $UCS_CURRENT_PLAYER_FILE` function initialize-current-player { #Если ранее записанный проигрыватель не встречаеться в списке зарегестрированных проигрывателей if ! echo $UCS_INTERESTED_PLAYERS | grep -q $UCS_CURRENT_PLAYER ; then #Делаем текущим проигрывателем первый из предпочитаемых UCS_CURRENT_PLAYER=`echo $UCS_PREFFERED_PLAYERS | grep -o "^\S*[^.]"` UCS_CURRENT_PLAYER=`echo $UCS_CURRENT_PLAYER | sed "s/\s//g"` echo Current player now is '"'$UCS_CURRENT_PLAYER'"' fi } #Мотаем проигрыватель на следующий function player-next { initial_player=$UCS_CURRENT_PLAYER for player in $UCS_INTERESTED_PLAYERS do if [ -z "$first_player" ]; then first_player=$player fi if [ "$previous_player" == "$UCS_CURRENT_PLAYER" ]; then UCS_CURRENT_PLAYER=$player break fi previous_player=$player done if [ "$initial_player" == "$UCS_CURRENT_PLAYER" ]; then UCS_CURRENT_PLAYER=$first_player fi echo $UCS_CURRENT_PLAYER > $UCS_CURRENT_PLAYER_FILE } #По аналогии на предыдущий function player-previous { initial_player=$UCS_CURRENT_PLAYER for player in $UCS_INTERESTED_PLAYERS do if [ -z "$first_player" ]; then first_player=$player fi if [ "$player" == "$UCS_CURRENT_PLAYER" ]; then UCS_CURRENT_PLAYER=$previous_player fi previous_player=$player done if [ -z "$UCS_CURRENT_PLAYER" ]; then UCS_CURRENT_PLAYER=$previous_player fi echo $UCS_CURRENT_PLAYER > $UCS_CURRENT_PLAYER_FILE }
Какой никакой, а интерфейс для переключения проигрывателей мы получили. Но хотелось бы что бы это ещё и выглядело прилично, и желательно как то проявляло себя на UI:
#То, что мы видим как имя плеера в списке действий, это на самом деле, имя #ярлыка, распологающегося по одному из этих путей: UCS_SYSTEM_WIDE_LAUNCHERS_PATH=/usr/share/applications UCS_LAUNCHERS_PATH=~/.local/share/applications #Найти полный путь для ярлыка заданного плеера function player-launcher { name=$1 launcher=$UCS_LAUNCHERS_PATH/$name system_wide_launcher=$UCS_SYSTEM_WIDE_LAUNCHERS_PATH/$name if [ -f "$launcher" ]; then echo $launcher else echo $system_wide_launcher fi } #Прочитать красивое имя проигрывателя из его ярлыка function player-display-name { name=$1 launcher=`player-launcher $name` if [ -f "$launcher" ]; then cat $launcher | grep -m 1 "^Name=" \ | sed "s/Name=//" else echo $player | sed "s/.desktop//" fi } #Выдрать оттуда же иконку, если таковая имееться function current-player-icon { launcher=`player-launcher $UCS_CURRENT_PLAYER` if [ -f "$launcher" ]; then cat $launcher | grep "Icon=" \ | sed "s/Icon=//" fi } #Вывести всю имеющуюся информацию в терминал и в виде уведомления function show-current-player { echo Curent player '"'$UCS_CURRENT_PLAYER'"' for player in $UCS_INTERESTED_PLAYERS do if [ $player == $UCS_CURRENT_PLAYER ]; then players=$players* fi player_name=`player-display-name $player` players=$players$player_name\\n done icon=`current-player-icon` if ! [ -z $icon ]; then icon="-i $icon" fi echo Icon is "$icon" notify-send "Players:" "$players" $icon -t 1 }
Как вы возможно заметили, я использовал команду notify-send, для вывода данных о списке плееров в виде уведомления. Это я к тому, что по умолчанию в Ubuntu для этой комманды не работает флаг -t, обозначающий таймаут который будет показываться уведомление, поэтому уведомления показываються неприлично долго. Исправить это можно, если воспользоваться этими инструкциями. Кто то возможно скажет — это не unix way, смешивать получение данных и их вывод на UI, я соглашусь, но дабы не усложнять использование скрипта я сделал всё именно так.
Используя полученные выше навыки работы с gdbus, можно без труда реализовать весь остальной функционал по управлению проигрывателем, по этому на этом подробно останавливаться я не буду, с тем что получилось в итоге можно ознакомиться на github. Отдельно, хочу лишь упомянуть о реализации сбора информации о текущем треке. Информация о состоянии проигрывателя, как оказалось, хранится в состоянии действия (gtk.Action), которое отвечает за запуск проигрывателя. Получить эту информацию можно с помощью метода org.gtk.Actions.Describe, и делается это так:
gdbus call --session --dest com.canonical.indicator.sound --object-path /com/canonical/indicator/sound \ --method org.gtk.Actions.Describe vkcomvkcom.desktop
В выводе этой команды содержиться минимальная необходимая информация о текущем состоянии проигрывателя и текущего трека, если таковой имееться.
После проделанной работы осталось только добавить горячие клавиши вызывающие действия из получившегося скрипта. Я использовал для этого CompizConfig, т.к. штатными средствами мне это не удалось проделать (Ubuntu 13.10), уж не знаю почему это не работает, но, надеюсь, вскоре это починят.
Установить CompizConfig можно испльзуя такую команду:
sudo apt-get install compizconfig-settings-manager
Выглядит это все примерно так:
На этом всё, спасибо за внимание.
ссылка на оригинал статьи http://habrahabr.ru/post/203504/
Добавить комментарий