Pull to refresh

Gmail Notifier своими руками

Configuring Linux *
image Начал я обустраиваться под Linux'ом и обнаружил, что мне сильно не хватает тех красивых попапов, уведомляющих о приходе новой почты, которые умеет показывать Google Talk под Windows. Гугление обнаружило несколько готовых скриптов, позволяющих реализовать подобные попапы (среди прочих: bash-скрипт, bash-скрипт + python-скрипт, плюс pidgin умеет проверять почту), но все подходы слегка уродливы были мне не по душе и требовали доработки напильником, поэтому я решил сделать всё с нуля (пусть будет уродливое, но своё!). О вещах, с которыми я столкнулся в процессе и пойдёт речь…



Предыстория: выбор WM (оконного менеджера)


В Linux управление окнами, рисование всяких рамочек и системных меню отдаётся X сервером на откуп так называемым оконным менеджерам. Поэтому под Linux, в отличие от Windows, можно гибко настраивать внешний вид окон, а также их поведение и размещение.

Надо сказать, что у всех «ненормальных программистов», есть свои собственные оконные менеджеры для Linux. У haskellистов есть xmonad, а у luaшников вроде меня есть Ion и скромно названный awesome. Наконец, у С-шников есть сверхлёгкий dwm, единственный способ переконфигурировать который — правка сорцов и пересборка.

Все вышеупомянутые оконные менеджеры относятся к классу tiling window manager (википедия предлагает перевод «фреймовый (или мозаичный) оконный менеджер»), небольшой обзор таких менеджеров недавно уже был на Хабре. Отличительная черта таких WM состоит в том, что отдельные окна размещаются на экране без перекрытий, покрывая его как мозаика:



На этом скриншоте видно, что как виканец и лунопоклонник, а так же человек уважающий скромность, я остановился на awesome.

Вообще у awesome есть некоторые проблемы с документацией (ну впрочем это бич многих не только открытых, но и проприентарных проектов). API документирован хорошо, но о некоторых вещах приходится догадываться или шарится в wiki.

Подробная процедура установки последних версий на Ubuntu описана, например, здесь.

Показ уведомлений


Я люблю двигаться в разработке задом наперёд, поэтому я сначала заинтересовался вопросом: «а как в awesome показать попап?». Ответ нашелся быстро: с помощью встроенной библиотеки naughty (я думаю читатель уже уловил общую направленность названий?). Например, если выполнить вот такой код в контексте awesome:
naughty.notify {
    title     = title, 
    text      = text, 
    timeout   = 10, 
    fg        = "#afbe01", 
    width     = 500, 
    icon      = icon_big,
    icon_size = 32
}

то в заданном месте экрана всплывёт в меру красивый попап с заголовком title, текстом сообщения text и иконкой icon_big.

Выполнять же Lua-код в контексте awesome можно с помощью утилиты awesome-client. Фактически это shell, который принимает на вход скрипты на lua (под одному на строчку) и последовательно выполняет их.

Таким образом, если хочется показать попап из некоторой независимо работающей программы/скрипта, то нужно подставить вместо title, text и icon_big их значения, затем передать получившуюся строчку (без переносов строк!) на вход awesome-client.

Поскольку большинство скриптов я пишу на lua и постоянно перемежать нормальный lua-код и формирование строчек для awesome-client'а мне не хочется, я используя особую метатабличную магию написал механизм позволяющий прозрачно вызывать код в контексте awesome. Достаточно в скрипте написать:
local naughty = proxy "naughty"

и дальнейшая работа с полями таблицы (вызовы и присваивания) naughty будут прозрачным образом выполняться в контексте awesome.

А еще naughty дружит dbus, но я пока без него обошелся.

Нам письмо?


Итак я умею показывать уведомление, но мне совершенно непонятно откуда взять информацию о приходе нового письма. Можно, конечно, разрешить POP доступ к ящику и проверять его содержимое каждые N секунд или (подсказано @merlin_rterm) можно открыть доступ по IMAP и подцепившись, получать нотификации ([RFC 2177] IMAP 4 IDLE Command). Можно взять Atom feed почтового ящика и каждые M секунд разбирать его скриптом на python'е или применить bash&wget&grep-fu. Ни одно из этих решений не показалось мне достаточно изящным. Я ввёл в гугл парочку запросов, содержавших среди прочих слова «gmail», «api» и «lua», но получил исключительно фигу. Отчаянье моё было столь велико, что я хотел уже бросить всё к чертям и повеситься совратиться в лагерь python'истов, но тут ко мне в голову постучалась светлая мысль: раз google talk показывает нотификации, раз pidgin проверяет почту, значит это как-то связано с протоколом XMPP, известным в народе под именем Jabber, поверх которого Google Talk и реализован! Окрылённый этим откровением я ввёл в гугл «google talk api» и через пару кликов мышью оказался на странице с говорящим названием Gmail Notifications.

Даже к финским скалам бурым обращаюсь с каламбуром.


СТАНС и (устар.) станц, станса, (фр. stance от ит. stanza) (лит.). В стихах — строфа, представляющая собой законченный синтаксический период.

XMPP — это такой забавный протокол, в котором Ромео и Джульета клиенты и серверы обмениваются между собой XML стансами. [RFC 3920] XMPP Core описывает стансы трёх типов: <iq/> (Info/Query), <presence/> (фактически информация о пользователе) и <message/>. Вообще XMPP Core достаточно простой протокол, но мне совершенно не улыбалось ботанить стандарты и возится с разбором XML, TLS, SASL и прочими деталями, и реализовывать всё с нуля, поэтому я вновь открыл гугль и сказал ему «xmpp lua».

Первая же ссылка привела меня на страницу Verse — lua-библиотеки, выстроенной поверх Str<>phe (названия опять таки с уклоном!), которая в свою очередь является С-библиотекой для работы с XMPP. Strophe берёт на себя всю низкоуровневую работу: соединение (с поддержкой TLS), авторизация (SASL) и разбор XML, а также обработка core станс. Verse предлагает удобный способ поддержки расширений (XEP = XMPP Extension Protocols, раньше известны были как JEP), возможность регистрации обработчиков событий, срабатывающих в момент прихода станс определённой формы.

Радость мою невозможно было описать никакими стансами… Я быстренько скачал обе библиотеки. Собрал Strophe, попробовал собрать Verse и обломился. Оказалось, что для сборки Verse, нужно особым образом патчить Strophe… Попатчил, пересобрал, пересобрал… Ура, пример работает!

Гугл, гугл, я — Алеф! Приём, приём!


Пробую подключится к гуглу:

conn = verse.connect("mister.aleph@gmail.com/epsilon3", password, handler, "talk.google.com");


mister.aleph@gmail.com/epsilon3 — это мой Jabber ID. Странный суффикс /epsilon3 определяет ресурс. Это позволяет одному mister.aleph@gmail.com зайти в Jabber из нескольких разных мест.

Запускую скрипт. Получаю фигу. Оказывается по умолчанию Strophe собирается без поддержки TLS, поэтому приходится всё еще раз пересобрать. Благо написано не на С++ с boost::mpl, поэтому не успеваю даже чая испить за время сборки.

Теперь всё логинится, и я даже вижу как мне приходят стансы <presence/> от моих контактов. Однако меня эти стансы не интересуют, ибо делать полноценный чат я не собираюсь (неполноценный возможно сделаю, но позже). Меня интересуют только стансы описанные в Gmail Notifications JEP.

Регистрирую в Verse новый протокол с двумя событиями:

local gmail = { xmlns = "google:mail:notify" };
verse.protocols.gmail = gmail;

gmail.events = {
  response = {
    { name = "iq"; type = "result"; xmlns = gmail.xmlns };
    mailbox  = "/iq/mailbox";
  };

  newmail = {
      { name = "iq"; type = "set" };

      id      = "/iq/@id";
      newmail = "/iq/new-mail";
  };
}


Событие response будет срабатывать тогда, когда сервер будет посылать мне станс-ответ на мой запрос содержимого ящика. В ноде mailbox будут описаны треды, с подробной информацией (отправители, метки, дата последнего сообщения). В обработчике этого события я буду перебирать эти треды и выводить нотификации о новой почте с помощью naughty и показывать в трее общий счетчик непрочитанных писем.

Событие newmail будет срабатывать тогда, когда сервер присылает мне стансу-уведомление об изменениях в ящике. В ответ на эту стансу я буду посылать стансу-запрос информации о содержимом ящика.

Собственно полный код можно посмотреть здесь.

Кстати, в описании Gmail Notifications JEP указано, что можно запрашивать не только непрочитанные сообщений, но вообще делать совершенно произвольный запрос, например, попросить все треды помеченные меткой #lua («label:#lua»).

Быстро сказка сказывается, да не быстро дело делается


Запускаю скрипт, получаю парочку попапов и segmentation fault, как только luavm захотела собрать мусор. Похоже Matthew Wild (автор Verse), не зря предупреждает на главной страничке:
Please note that this is ALPHA status software. It does have crasher bugs, which I am aware of...

Но не на тех напал, нам русским ненормальным луашникам всё по плечу. Вооружаюсь волшебным valgrind'ом, gdb, бумажкой, двадцатигранным кубиком, чётками, оставшимся с НГ бутыльком коньяка, а также мозгом… Через полчаса segmentation fault пропадает, а память почти перестаёт утекать в неизвестном направлении.

Оформление


Дизайнер, иллюстратор или художник из меня никакой, поэтому я вновь обратился к гуглу и обнаружил на сайте Сhris'а Ivarson'а набор разноцветных gmail-овских иконок (красный, синий, зеленый, черный, розовый) в формате ICNS. Эти иконки я перегнал в PNG с помощью утилиты icns2png. Цвет текста я взял с замечательной Daft Punk'овской обоины.

Итого


Перекладываю готовый скрипт вместе с иконками в ~/.config/awesome/gmail и добавляю его запуск в rc.lua:



Победа!

Некоторые банальные выводы

  • Глаза боятся, а руки делают;
  • Lua — rulezz;
  • Linux пригоден для жизни без скафандра;
  • Если берёте опенсурсную библиотеку, будьте готовы взять напильник;
  • Собака — лучший друг человека. Гугл — лучший друг программиста.
  • Открытые API — rulezz!
Tags:
Hubs:
Total votes 137: ↑128 and ↓9 +119
Views 4.8K
Comments Comments 111