
Предыстория: выбор 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», но получил исключительно фигу. Отчаянье моё было столь велико, что я хотел уже бросить всё к чертям и
Даже к финским скалам бурым обращаюсь с каламбуром.
СТАНС и (устар.) станц, станса, (фр. stance от ит. stanza) (лит.). В стихах — строфа, представляющая собой законченный синтаксический период.
XMPP — это такой забавный протокол, в котором
Первая же ссылка привела меня на страницу 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!