Пишем Extension для Gnome 3

Вступление

Откуда взялась идея. Однажды пришлось моим родителям пересесть с Windows XP на Linux Mint с KDE. В старой операционке они привыкли пользоваться интернет-телефонией от Sipnet.ru. У этого провайдера есть своя программа Sippoint mini (только под Windows), которая сразу отображает баланс на счёте. Естесственно, в других софтофонах этой фишки нет. API тоже никакого нет. Нужно только лезть на их сайт в свой личный кабинет и там смотреть свой баланс. Не быстро и не удобно (никому не нравится вбивать логины и пароли, - особенно старшему поколению). Нужно было организовать быструю проверку баланса в Sipnet.ru с отображением прямо на рабочем столе.

Парсим страницу на bash'е

Идея проверки баланса состоит в следующем. Некая программа периодически или по запросу заходит в личный кабинет, находит там баланс и сохраняет его в файл. Потом этот файл можно прочитать либо лично открыв, либо направив на него другую программу, которая удобно покажет баланс. Приступаем к первой части и пишем скрипт, который стащит нам нужную информацию. Нужно только подставить ваш ID sipnet и пароль (те же данные, которые нужно вводить для входа в личный кабинет):

#!/bin/bash
# Изменим среду исполнения,т.к. запуск будет из службы
source /root/.bash_profile
# Скачиваем страницу личного кабинета с состояниеем счёта
curl -d "CabinetAction=login&Name=вашID&Password=вашпароль"
https://customer.sipnet.ru/cabinet/ > /tmp/sipacc1.tmp&&
# Удаляем всё, кроме суммы на счёте и сохраняем это в файл
# С помощью grep отбираем только строки с <div>
# Потом оставляем только первую строку,
# убираем табуляцию, пробелы и перенос строки
cat /tmp/sipacc1.tmp|grep '<div>'|head -n1|expand|tr -d " "|\
tr -d "\n" > /tmp/sipacc.tmp&& \
# Из оставшегося отбираем только то, что находится между
# <div> и &nbsp; А это и есть баланс на счёте
sed -n -i 's/<div>\(.*\)&nbsp;.*/\1/p' /tmp/sipacc.tmp;

В результате работы этого скрипта получится файл /tmp/sipacc.tmp, который содержит текущий баланс. Этот скрипт будет рабочим ровно до тех пор, пока web-мастер sipnet не изменит шаблон страницы. Тогда нужно будет переделывать скрипт.

Включаем скрипт в задание

Так получилось, что сразу под рукой не оказалось компьютера с нужной мне KDE, но с Gnome 3. Было решено сделать сначала extension для Gnome 3, а потом plasma виджет для KDE.
Помещаем скрипт в /usr/local/bin/sipnet-get-balance.sh, даём права на исполнение.
Затем средствами Systemd делаем задание. В директории /etc/systemd/system создаём два файла: sipnet-get-balance.timer (определяет, когда наш сервис будет запускаться) и sipnet-get-balance.service (описание сервиса).
Содержимое первого файла:

[Unit]
Description=Check sipnet balance every hour
[Timer]
# Time to wait after booting before we run first time
OnBootSec=1min
# Time between running each consecutive time
OnUnitActiveSec=1h
Unit=sipnet-get-balance.service
[Install]
WantedBy=multi-user.target

В этом файле определено, что первый запуск службы будет через одну минуту после загрузки компьютера и потом будет повторяться каждый час (я редко звоню).
Второй файл:

[Unit]
Description=Check sipnet balance
[Service]
Type=simple
User=root
ExecStart=/usr/local/bin/sipnet-get-balance.sh

Тут просто указывается наш скрипт, который скачивает для нас баланс. Активируется таймер командой:


systemctl enable sipnet-get-balance.timer
Действия вступят после перезагрузки или можно сразу запустить командой:

systemctl start sipnet-get-balance.timer
Если изменять файлы timer и service после их сохранения, то перед их перезапуском понадобится выполнить команду:

systemctl daemon-reload
Статус нашего сервиса и таймера можно посмотреть командами:

systemctl status sipnet-get-balance.service -l
и

systemctl --full | grep sipnet-get-balance

Создаём extension для Gnome 3

Я выполнил команду:

gnome-shell-extension-tool --create-extension
Ответил на вопросы программы и создались файлы в директории /home/oleg/.local/share/gnome-shell/extensions/subspam@mail.ru: extension.js, metadata.json и stylesheet.css. Это готовый демо-пример. Если перезайти в Gnome Shell (Alt+F2 и команда r), то расширение появится в списке раширений (останется его только включить):

Далее я модифицировал демо-пример так, чтобы вместо иконки на кнопке отображался баланс, который автоматически менятся при изменении информации в нашем файле (его нам готовит скрипт) и принудительно при нажатии на кнопку.
Информации для разработчика расширений Gnome 3 довольно мало. Примеров, да ещё и с объяснениями на русском, так вообще нет. Вот восполняю. Полезные ссылки:
Ссылки на описания различных библиотек и правил: https://developer.gnome.org/references и http://www.roojs.com/seed/gir-1.2-gtk-3.0/gjs/index.html
Кратко о создании расширений Gnome 3 https://wiki.gnome.org/Projects/GnomeShell/Extensions. Здесь же ссылки на статьи и программы, полезные при разработке расширений. Например, мне пригодилась консоль Looking Glass и само собой программа GNOME Tweak Tool.
Коротко в целом о Gnome Shell https://wiki.gnome.org/Projects/GnomeShell
Про сам язык программирования https://wiki.gnome.org/action/show/Projects/Gjs?action=show&redirect=Gjs
Переводной пост на Хабре http://habrahabr.ru/post/120682/ с продолжением.
А дальше пришлось ковырять уже готовые extensions и выискивать полезное для себя. Благо можно их код смотреть прямо в браузере из git'а
Ниже приведу код, который у меня получился с небольшими пояснениями.

metadata.json

Остался без изменений после генерации:

{
"shell-version": ["3.10.4"],
"uuid": "subspam@mail.ru",
"name": "Sipnet balance",
"description": "Show sipnet balance on top panel"
}

extension.js

Тут вся программа. Приведу пояснения к тому, что я изменил по отношению к автоматически сгенерированному демо-примеру.
Расширил объявления:

const St = imports.gi.St;
const Main = imports.ui.main;
const Tweener = imports.ui.tweener;
const Lang = imports.lang;
const Gio = imports.gi.Gio;
const GLib = imports.gi.GLib;
let text, button;
let balanceText;

Функцию _hideHello() решил не перименовывать и оставить как есть.
Фунцию _showHello() изменил (название оставил). Исправил текст сообщения. И добавил механизм обновления надписи на кнопке (это и есть наш баланс). Получилась так:

function _showHello() {
    if (!text) {
        text = new St.Label({ style_class: 'helloworld-label', 
         text: "Баланс Sipnet изменился!" });
        Main.uiGroup.add_actor(text);
    }   
    text.opacity = 255;
    let monitor = Main.layoutManager.primaryMonitor;
    text.set_position(Math.floor(monitor.width / 2 - text.width / 2),
                    Math.floor(monitor.height / 2 - text.height / 2));
    Tweener.addTween(text,
                     { opacity: 0,
                       time: 2,
                       transition: 'easeOutQuad',
                       onComplete: _hideHello });
    
    let newBalance = refreshBalance();
    balanceText.set_text(newBalance);
}

Создал функцию, которая проверяет, есть ли наш файл с балансом, открывает его и считывает данные в строковую переменную balance:

function refreshBalance() {
  // Цепляем файл, который нам подготовил скрипт
  let balanceFile = GLib.build_filenamev(["/tmp/sipacc.tmp"]);
  let balance;
  if (balanceFile) {
    if (GLib.file_test(balanceFile, GLib.FileTest.EXISTS)) {
      // Пытаемся прочитать содержимое нашего файла
      let balanceFileData;
      try {
        balanceFileData = GLib.file_get_contents(balanceFile, null, 0);
      } catch (e) {
        Main.notifyError("Error reading file", e.message);
        return false;
      }
      if (balanceFileData[0]) {
        // Анализируем содержимое
        balance = String(balanceFileData[1]) + '$';
      }
    }      
  } else {
    balance = "...";
  }
  return balance;
}
Создал функцию, которая нужна для принудительной проверки баланса (будет выполняться по нажатию на кнопку):

function updateNow() {
  // Выполним скрипт, обновляющий баланс из личного кабинета Sipnet
  GLib.spawn_command_line_sync("sh /usr/local/bin/sipnet-get-balance.sh"); 
  let newBalance = refreshBalance();
  balanceText.set_text(newBalance);
}
В функции инициализации создаётся наша кнопка (она же информер с балансом), на неё цепляется обработчик нажатия (вызов принудительного обновления баланса updateNow()). И делается хитрая вещь. Назначается некий балюдатель за целевым файлом. Если его содержимое изменится (а за это отвечает наш bash-скрипт в таймере systemd), то тут же будет выполнена функция обновления текста кнопки (т.е. нашего баланса).

function init() {
  // Выполним скрипт, обновляющий баланс из личного кабинета Sipnet
  GLib.spawn_command_line_sync("sh /usr/local/bin/sipnet-get-balance.sh");
 
  button = new St.Bin({ style_class: 'panel-button',
                        reactive: true,
                        can_focus: true,
                        x_fill: true,
                        y_fill: false,
                        track_hover: true });
  
  let balance = refreshBalance();
                          
  balanceText = new St.Label({style_class: 'sip-label', text: balance});
  button.set_child(balanceText);
  button.connect('button-press-event', updateNow);
  
  let file = Gio.file_new_for_path("/tmp/sipacc.tmp");
  fileMonitor = file.monitor(Gio.FileMonitorFlags.NONE, null);
  fileMonitor.connect('changed', Lang.bind(this, refreshBalance));    
}

stylesheet.css

Добавил стиль для надписи на кнопке. В целом получилось так:

.helloworld-label {
    font-size: 36px;
    font-weight: bold;
    color: #ffffff;
    background-color: rgba(10,10,10,0.7);
    border-radius: 5px;
    padding: .5em;
}
.sip-label {
  font-size: 14px;
} 
В итоге в верхней панели отображается баланс sipnet.

В данном подходе много ещё что можно усовершенствовать, но старт дан. Аналогично можно добавить мониторинг нужной информации посредством extensions Gnome 3. Теперь займусь виджетом для KDE...

Комментарии

Популярные сообщения из этого блога

Пропорциональное распределение суммы

Битрикс: своя геолокация

Bitrix24 API - разбор демо приложения третьего типа