Привет, Хабр!
Для меня эта фраза сродни hello world, так как я добрался наконец до своей первой публикации. Долго откладывал этот замечательный момент, так как и писать было не о чем, а обсасывать то, что уже по куче раз обсосано, тоже не хотелось. Вообще для своей первой публикации хотелось что-то оригинальное, полезное другим и содержащее какой-то челленж и решение проблем. И вот я уже могу поделиться этим. Теперь обо всем по порядку.
Началось все с того, что некоторое время назад я на рабочем компе накатил себе Linux Mint. Многие наверняка знают, что Pidgin с плагином Sipe вполне годная замена Microsoft Lync (сейчас называется Skype for business) для Linux систем. В силу специфики работы мне часто приходится участвовать в sip-конференциях, и в бытность виндузятника вход в конференции был элементарным: получаем приглашение по почте, кликаем на ссылку входа, готово мы внутри.
При переходе натемную сторону Linux все несколько усложнилось: вход в конференции в Pidgin конечно же тоже есть, но там для этого нужно в свойствах вашего sip-аккаунта выбрать пункт присоединения к конференции в меню и в открывшемся окне вставить ссылку на конференцию либо ввести имя организатора и conf id. И через какое-то время я начал задумываться: «а нельзя ли как-то это упростить». Ага, скажете вы, на кой черт тебе это понадобилось, сидел бы себе на винде и в ус не дул.
«Втемяшится в башку какая блажь — колом ее оттудова не выбьешь» — так говорил Некрасов в своем произведении «Кому на Руси жить хорошо».
Итак, раз уж мысль попала в голову, то через какое-то время возникла и первая идея для реализации. Казалось все просто — нужно перехватывать обращение к ссылкам meet.company.com/user/confid — поставить у себя на тачке локальный процесс веб-приложения на 127.0.0.1 и в /etc/hosts занести статическую запись для домена компании, через который производится вход в конференции, указывающий на localhost. Дальше этот веб-сервер должен обработать пришедшую ему ссылку и как-то передать внутрь Pidgin (сразу скажу, что на этом этапе у меня не было еще понимания как мне это ему вообще отдать). Решение конечно попахивает костылями, но мы же программисты, костыли нас не пугают (щютка).
Потом случайно я как-то открыл ссылку на приглашение в Google Chrome (а обычно я всегда использую Mozilla Firefox). И на мое удивление веб-страничка выглядела совсем иначе — не было формы ввода пользовательских данных и сразу после входа на страницу выходил запрос на открытие чего-то через xdg-open. Ради интереса нажимаю «да» и выходит сообщение об ошибке — ссылка lync15:confjoin?url=https://meet.company.com/user/confid не может быть открыта. Хмм. Что это за xdg-open такой и что ему нужно для того, чтобы такие ссылки открывались?Вскрытие чтение документации показало, что это обработчик графической оболочки, который помогает запускать ассоциированные приложения либо с протоколами для uri scheme, либо с определенными типами файлов. Ассоциации настраиваются через сопоставление mime-type. Итак, мы видим, что у нас запускается поиск сопоставленного приложения для uri scheme с именем lync15 и ссылка передается в xdg-open, который дальше по идее должен передать ее какому-то приложению, которое отвечает за данный тип ссылок. Которого у нас, конечно, в системе нет. А раз нет, то как поступают в мире опенсорса? Правильно, мы напишем его сами.
Дальнейшее погружение в мир Linux и особенно в изучение того, как работает графическая оболочка (desktop environment, DE), к слову у меня это Xfce в Linux Mint, показало что приложения и ассоциированный с ним mime-type прописывается обычно прямо в файлах ярлыков с расширением .desktop. Ну а почему бы и нет, создаю простой ярлык приложения, который должен просто запустить баш-скрипт и вывести переданный ему аргумент на консоль, привожу только сам файл ярлыка:
запускаю xdg-open из консоли с передачей той же ссылки, какая идет из браузера и… облом. Снова говорит что не может обработать ссылку.
Как оказалось я не обновил каталог ассоциированных mime-type с моим приложением. Делается это простой командой:
которая просто редактирует файл ~/.config/mimeapps.list.
Попытка номер 2 с вызовом xdg-open — и снова неудача. Ничего, трудности нас не пугают, а только подогревают интерес. И вооружившись всей мощью баша (т.е. трассировкой) ныряем с головой в отладку. Тут важно отметить, что xdg-open является просто шелл-скриптом.
Анализируя вывод после трассировки становится немного понятно, что дальше управление передается в exo-open. А это уже бинарный файл и разбираться почему он возвращает неуспешный код возврата при передаче ему ссылки в аргументе уже сложнее.
Пробежав по внутренностям xdg-open я выяснил, что он анализирует разные параметры окружения и передает управление дальше либо каким-то инструментам для открывания файлов\ссылок специфичных для конкретного DE, либо у него имеется fallback в виде функции open_generic
Быстро впиливаю сюда небольшой хак с анализом переданного аргумента и если там находится наша специфическая подстрока lync15:, то передаем сразу управление в функцию open_generic.
Попытка номер 3 и вы думаете заработало? Ага, сейчас, как же. Но сообщение об ошибке уже поменялось, это уже прогресс — сейчас он мне говорил что файл не найден и в виде файла писал мне ту самую ссылку, переданную в виде аргумента.
На этот раз дело оказалось в функции is_file_url_or_path, которая анализирует передана на вход ссылка на файл file:// либо путь до файла или что-то иное. И проверка не работала верно из-за того что наш префикс (url scheme) имеет цифры, а регулярное выражение проверяется только набор символов, состоящий из :alpha: точки и тире. После консультация со стандартом rfc3986 для uniform resource identifier стало понятно что на этот раз Майкрософт ничего не нарушает (хотя у меня была такая версия). Просто класс символов :alpha: содержит только буквы латинского алфавита. Быстро меняю проверку по регулярке на alphanumeric. Готово, вы восхитительны, все наконец запускается, управление после всех проверок отдается в наш скрипт-приложение, на консоль выводится наша ссылка, все как полагается. После этого начинаю подозревать, что все проблемы у exo-open тоже из-за валидации формата ссылки из-за цифр в схеме. Для проверки гипотезы меняю регистрацию mime-type у приложения на просто схему lync и вуаля — все работает и без переопределения функции open_xfce. Но нам это никак не поможет, потому что веб-страница для входа в конференцию создает именно ссылку с lync15.
Итак, первая часть пути пройдена. Мы умеем перехватывать вызов ссылки и дальше ее нужно как-то обработать и передать внутрь Pidgin. Для того чтобы понять как оно работает внутри при вводе данных по ссылке в меню «присоединения к конференции» я склонировал гит репозиторий проекта Sipe и приготовился снова нырять в код. Но тут меня к счастью привлекли скрипты в каталоге contrib/dbus/:
Оказывается плагин Sipe доступен для взаимодействия через dbus (desktop bus) и внутри скриптов прямо есть примеры присоединения в конференцию по ссылке, либо через имя организатора и conf-id, либо можно инициировать звонок через sip. Это же как раз то чего нам не хватало.
Раз уж есть готовые примеры на перл, то я решил просто использовать sipe-join-conference-with-uri.pl и немного доработать под себя. Писать на перл я умею, поэтому особых сложностей это не доставило.
После тестирования скрипта отдельно, я вписал его вызов в файл lync.desktop. И это была победа! При входе на страницу присоединения к конференции и разрешения запуска xdg-open всплывающее окно конференции из Pidgin открывалось автоматически. Как же я ликовал.
Воодушевленный успехом, я решил сделать то же самое и для своего основного браузера Mozilla Firefox. При входе через лису открывается страничка для авторизации и еще в самом низу есть кнопка join using office communicator. Она-то и привлекла мое внимание. При клике на нее в браузере происходит переход по адресу:
на что он мне любезно говорит, что не знает как ее открыть и, возможно, у меня нет ассоциированного приложения для такого протокола. Ну это мы уже проходили.
Быстренько регистрирую свое приложение-скрипт еще и для uri-схемы conf и… ничего не происходит. Браузер продолжает жаловаться, что нет приложения, которое обрабатывает мои ссылки. При этом вызов из консоли xdg-open с параметрами работает превосходно.
«Set custom protocol handler in firefox» — с этим вопросом я вышел в Интернет. Прошерстив несколько обсуждений на stackoverflow (а куда же без него), вроде ответ нашелся. Нужно создать специальный параметр в about:config (конечно заменив foo на conf):
Создаем, открываем ссылку и… не тут-то было. Браузер как ни в чем ни бывало говорит что не знает наше приложение.
Читаю официальную документацию по регистрации протокола у Mozilla, есть вариант прописать асоциации в самом gnome desktop (заменив конечно foo на conf):
Регистрирую, открываю браузер… и снова борода.
Тут на глаза попадается строчка из документации:
— Семен Семеныч
— Ааа
Мы же не кликаем по ссылке, а просто веб-страница делает смену window.location через javascript. Пишу простой html файл со ссылкой на conf протокол, открываю в браузере, кликаю по ссылке — Yos! Открывается окно с вопросом в каком приложении нужно открыть нашу ссылку и там в списке у нас уже есть наше приложение Lync — мы же его честно зарегистрировали всеми возможными способами. Там же в окошке есть галочка «запомнить выбор и всегда открывать ссылки в нашем приложении», отмечаем, нажимаем ок. И это вторая победа — окно конференции открывается. При этом уже открытие конференций работает и не только при клике на ссылку, а и при переходе из нужной нам страницы присоединения в конференцию.
Потом я проверил, удаление параметров network.protocol-handler.expose.conf никак не повляло на работу протокола в лисе. Ссылки продолжали работать.
Я залил все свои наработки в репозиторий на гитхаб, ссылки на все ресурсы будут в конце статьи.
Мне будет интересно получить обратную связь от тех кто захочет воспользоваться моими наработками. Сразу отмечу, что я все делал разработку только под свою систему Linux Mint, поэтому какие-то другие дистрибутивы или десктопы могут не заработать в том варианте. Вернее я даже почти уверен в этом, потому что я пропатчил в xdg-open только 1 функцию, относящуюся только к моему DE. Если вы захотите добавить поддержку других систем или декстопов, пишите мне пул-реквесты в гитхабе.
Реализация всего проекта заняла 1 вечер.
Ссылки:
Для меня эта фраза сродни hello world, так как я добрался наконец до своей первой публикации. Долго откладывал этот замечательный момент, так как и писать было не о чем, а обсасывать то, что уже по куче раз обсосано, тоже не хотелось. Вообще для своей первой публикации хотелось что-то оригинальное, полезное другим и содержащее какой-то челленж и решение проблем. И вот я уже могу поделиться этим. Теперь обо всем по порядку.
Вступление
Началось все с того, что некоторое время назад я на рабочем компе накатил себе Linux Mint. Многие наверняка знают, что Pidgin с плагином Sipe вполне годная замена Microsoft Lync (сейчас называется Skype for business) для Linux систем. В силу специфики работы мне часто приходится участвовать в sip-конференциях, и в бытность виндузятника вход в конференции был элементарным: получаем приглашение по почте, кликаем на ссылку входа, готово мы внутри.
При переходе на
Шаг 1. Исследование
«Втемяшится в башку какая блажь — колом ее оттудова не выбьешь» — так говорил Некрасов в своем произведении «Кому на Руси жить хорошо».
Итак, раз уж мысль попала в голову, то через какое-то время возникла и первая идея для реализации. Казалось все просто — нужно перехватывать обращение к ссылкам meet.company.com/user/confid — поставить у себя на тачке локальный процесс веб-приложения на 127.0.0.1 и в /etc/hosts занести статическую запись для домена компании, через который производится вход в конференции, указывающий на localhost. Дальше этот веб-сервер должен обработать пришедшую ему ссылку и как-то передать внутрь Pidgin (сразу скажу, что на этом этапе у меня не было еще понимания как мне это ему вообще отдать). Решение конечно попахивает костылями, но мы же программисты, костыли нас не пугают (щютка).
Потом случайно я как-то открыл ссылку на приглашение в Google Chrome (а обычно я всегда использую Mozilla Firefox). И на мое удивление веб-страничка выглядела совсем иначе — не было формы ввода пользовательских данных и сразу после входа на страницу выходил запрос на открытие чего-то через xdg-open. Ради интереса нажимаю «да» и выходит сообщение об ошибке — ссылка lync15:confjoin?url=https://meet.company.com/user/confid не может быть открыта. Хмм. Что это за xdg-open такой и что ему нужно для того, чтобы такие ссылки открывались?
Дальнейшее погружение в мир Linux и особенно в изучение того, как работает графическая оболочка (desktop environment, DE), к слову у меня это Xfce в Linux Mint, показало что приложения и ассоциированный с ним mime-type прописывается обычно прямо в файлах ярлыков с расширением .desktop. Ну а почему бы и нет, создаю простой ярлык приложения, который должен просто запустить баш-скрипт и вывести переданный ему аргумент на консоль, привожу только сам файл ярлыка:
[Desktop Entry]
Name=Lync
Exec=/usr/local/bin/lync.sh %u
Type=Application
Terminal=false
Categories=Network;InstantMessaging;
MimeType=x-scheme-handler/lync15;
запускаю xdg-open из консоли с передачей той же ссылки, какая идет из браузера и… облом. Снова говорит что не может обработать ссылку.
Как оказалось я не обновил каталог ассоциированных mime-type с моим приложением. Делается это простой командой:
xdg-mime default lync.desktop x-scheme-handler/lync15
которая просто редактирует файл ~/.config/mimeapps.list.
Попытка номер 2 с вызовом xdg-open — и снова неудача. Ничего, трудности нас не пугают, а только подогревают интерес. И вооружившись всей мощью баша (т.е. трассировкой) ныряем с головой в отладку. Тут важно отметить, что xdg-open является просто шелл-скриптом.
bash -x xdg-open $url
Анализируя вывод после трассировки становится немного понятно, что дальше управление передается в exo-open. А это уже бинарный файл и разбираться почему он возвращает неуспешный код возврата при передаче ему ссылки в аргументе уже сложнее.
Пробежав по внутренностям xdg-open я выяснил, что он анализирует разные параметры окружения и передает управление дальше либо каким-то инструментам для открывания файлов\ссылок специфичных для конкретного DE, либо у него имеется fallback в виде функции open_generic
open_xfce()
{
if exo-open --help 2>/dev/null 1>&2; then
exo-open "$1"
elif gio help open 2>/dev/null 1>&2; then
gio open "$1"
elif gvfs-open --help 2>/dev/null 1>&2; then
gvfs-open "$1"
else
open_generic "$1"
fi
if [ $? -eq 0 ]; then
exit_success
else
exit_failure_operation_failed
fi
}
Быстро впиливаю сюда небольшой хак с анализом переданного аргумента и если там находится наша специфическая подстрока lync15:, то передаем сразу управление в функцию open_generic.
Попытка номер 3 и вы думаете заработало? Ага, сейчас, как же. Но сообщение об ошибке уже поменялось, это уже прогресс — сейчас он мне говорил что файл не найден и в виде файла писал мне ту самую ссылку, переданную в виде аргумента.
На этот раз дело оказалось в функции is_file_url_or_path, которая анализирует передана на вход ссылка на файл file:// либо путь до файла или что-то иное. И проверка не работала верно из-за того что наш префикс (url scheme) имеет цифры, а регулярное выражение проверяется только набор символов, состоящий из :alpha: точки и тире. После консультация со стандартом rfc3986 для uniform resource identifier стало понятно что на этот раз Майкрософт ничего не нарушает (хотя у меня была такая версия). Просто класс символов :alpha: содержит только буквы латинского алфавита. Быстро меняю проверку по регулярке на alphanumeric. Готово, вы восхитительны, все наконец запускается, управление после всех проверок отдается в наш скрипт-приложение, на консоль выводится наша ссылка, все как полагается. После этого начинаю подозревать, что все проблемы у exo-open тоже из-за валидации формата ссылки из-за цифр в схеме. Для проверки гипотезы меняю регистрацию mime-type у приложения на просто схему lync и вуаля — все работает и без переопределения функции open_xfce. Но нам это никак не поможет, потому что веб-страница для входа в конференцию создает именно ссылку с lync15.
Итак, первая часть пути пройдена. Мы умеем перехватывать вызов ссылки и дальше ее нужно как-то обработать и передать внутрь Pidgin. Для того чтобы понять как оно работает внутри при вводе данных по ссылке в меню «присоединения к конференции» я склонировал гит репозиторий проекта Sipe и приготовился снова нырять в код. Но тут меня к счастью привлекли скрипты в каталоге contrib/dbus/:
- sipe-join-conference-with-uri.pl
- sipe-join-conference-with-organizer-and-id.pl
- sipe-call-phone-number.pl
- SipeHelper.pm
Оказывается плагин Sipe доступен для взаимодействия через dbus (desktop bus) и внутри скриптов прямо есть примеры присоединения в конференцию по ссылке, либо через имя организатора и conf-id, либо можно инициировать звонок через sip. Это же как раз то чего нам не хватало.
Шаг 2. Реализация обработчика для автоджойна
Раз уж есть готовые примеры на перл, то я решил просто использовать sipe-join-conference-with-uri.pl и немного доработать под себя. Писать на перл я умею, поэтому особых сложностей это не доставило.
После тестирования скрипта отдельно, я вписал его вызов в файл lync.desktop. И это была победа! При входе на страницу присоединения к конференции и разрешения запуска xdg-open всплывающее окно конференции из Pidgin открывалось автоматически. Как же я ликовал.
Воодушевленный успехом, я решил сделать то же самое и для своего основного браузера Mozilla Firefox. При входе через лису открывается страничка для авторизации и еще в самом низу есть кнопка join using office communicator. Она-то и привлекла мое внимание. При клике на нее в браузере происходит переход по адресу:
conf:sip:{user};gruu;opaque=app:conf:focus:id:{conf-id}%3Frequired-media=audio
на что он мне любезно говорит, что не знает как ее открыть и, возможно, у меня нет ассоциированного приложения для такого протокола. Ну это мы уже проходили.
Быстренько регистрирую свое приложение-скрипт еще и для uri-схемы conf и… ничего не происходит. Браузер продолжает жаловаться, что нет приложения, которое обрабатывает мои ссылки. При этом вызов из консоли xdg-open с параметрами работает превосходно.
«Set custom protocol handler in firefox» — с этим вопросом я вышел в Интернет. Прошерстив несколько обсуждений на stackoverflow (а куда же без него), вроде ответ нашелся. Нужно создать специальный параметр в about:config (конечно заменив foo на conf):
network.protocol-handler.expose.foo = false
Создаем, открываем ссылку и… не тут-то было. Браузер как ни в чем ни бывало говорит что не знает наше приложение.
Читаю официальную документацию по регистрации протокола у Mozilla, есть вариант прописать асоциации в самом gnome desktop (заменив конечно foo на conf):
gconftool-2 -s /desktop/gnome/url-handlers/foo/command '/path/to/app %s' --type String
gconftool-2 -s /desktop/gnome/url-handlers/foo/enabled --type Boolean true
Регистрирую, открываю браузер… и снова борода.
Тут на глаза попадается строчка из документации:
Next time you click a link of protocol-type foo you will be asked which application to open it with.
— Семен Семеныч
— Ааа
Мы же не кликаем по ссылке, а просто веб-страница делает смену window.location через javascript. Пишу простой html файл со ссылкой на conf протокол, открываю в браузере, кликаю по ссылке — Yos! Открывается окно с вопросом в каком приложении нужно открыть нашу ссылку и там в списке у нас уже есть наше приложение Lync — мы же его честно зарегистрировали всеми возможными способами. Там же в окошке есть галочка «запомнить выбор и всегда открывать ссылки в нашем приложении», отмечаем, нажимаем ок. И это вторая победа — окно конференции открывается. При этом уже открытие конференций работает и не только при клике на ссылку, а и при переходе из нужной нам страницы присоединения в конференцию.
Потом я проверил, удаление параметров network.protocol-handler.expose.conf никак не повляло на работу протокола в лисе. Ссылки продолжали работать.
Заключение
Я залил все свои наработки в репозиторий на гитхаб, ссылки на все ресурсы будут в конце статьи.
Мне будет интересно получить обратную связь от тех кто захочет воспользоваться моими наработками. Сразу отмечу, что я все делал разработку только под свою систему Linux Mint, поэтому какие-то другие дистрибутивы или десктопы могут не заработать в том варианте. Вернее я даже почти уверен в этом, потому что я пропатчил в xdg-open только 1 функцию, относящуюся только к моему DE. Если вы захотите добавить поддержку других систем или декстопов, пишите мне пул-реквесты в гитхабе.
Реализация всего проекта заняла 1 вечер.
Ссылки:
- Регистрация протокола в Mozilla
- D-bus(wikipedia)
- rfc3986 URI
- проект на Github