Pull to refresh

Пишем расширение Firefox для интеграции с панелью Unity

Reading time 12 min
Views 2K
Narwhals, Narwhals, swimming in the ocean, causing a commotion coz they are so awesome!С выходом Ubuntu 11.04 оболочка Unity достаточно неожиданно заняла место оболочки по умолчанию. Появление Unity в Ubuntu Netbook Edition, несмотря на медленную скорость работы, было довольно обоснованным: оно позволило эффективно использовать маленькие экраны нетбуков, благодаря, например, вертикальной панели для переключения между запущенными программами. Почему же возникла необходимость переноса Unity на десктопы? Ответить на этот вопрос можно было бы уже сейчас. Однако такой неполный, без готовых примеров, субъективный ответ вряд ли бы удовлетворил пользователя, которому на данный момент пришлось столкнуться с неудобством перехода. Поэтому речь пойдёт о том, как улучшить, а не о том, как и зачем пережить.

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

Итак, что нам понадобится:
  1. Расширение Restartless Restart. В процессе разработки понадобится часто перезагружать браузер для тестирования расширения
  2. Архиватор, позволяющий редактировать файлы прямо в архиве, без ручной перепаковки. С этим справляется стандартный архиватор в Ubuntu File-Roller
  3. Текстовый редактор

Собственно, для такого простого расширения хватит. Возможно вам также пригодится расширение Extension Developer.

Теперь нужно создать структуру нашего расширения:
unityfox
-- chrome  
---- content
------main.xul
-- chrome.manifest 
-- install.rdf


Наше расширение не использует локализацию или настройки, поэтому стандартные директории locale и preferences нам не пригодятся.

Начинается всё с файла install.rdf. В нём принято описывать краткую информацию о расширении: название, описание, версию, авторов, уникальный идентификатор расширения и набор поддерживаемых приложений (одно и то же расширение может работать, например, в Seamonkey и Firefox):
<?xml version="1.0"?>
 
<RDF xmlns="http:&#47&#47www.w3.org/1999/02/22-rdf-syntax-ns#"
     xmlns:em="http:&#47&#47www.mozilla.org/2004/em-rdf#">
 
  <Description about="urn:mozilla:install-manifest">
    <em:id>unityfox@mozilla.org</em:id>
    <em:version>0.1.3</em:version>
    <em:type>2</em:type>
 
    <em:targetApplication>
      <Description>
        <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
        <em:minVersion>4.0</em:minVersion>
        <em:maxVersion>6.0a1</em:maxVersion>
      </Description>
    </em:targetApplication>
 
    <em:name>unityfox</em:name>
    <em:description>Integration with Ubuntu Unity panel</em:description>
    <em:creator>Lockal</em:creator>
  </Description>      
</RDF>


Структура файла должна быть понятна без объяснений. Маленькое уточнение: <em:type>2</em:type> показывает, что этот файл является именно расширением, а не темой оформления, например. {ec8030f7-c20a-464f-9b0e-13a3a9e97384} — код браузера Firefox. Нижний предел версии 4.0 обусловлен тем, что мы будем использовать функции js-ctypes для обмена сообщениями с библиотекой Unity.

Завершив написание install.rdf, можно приступить непосредственно к созданию рабочего кода. Для того, чтобы при создании окна выполнялся наш javascript-код из файла main.xul, нужно описать в файле chrome.manifest процедуру наложения (overlay):
content     unityfox    chrome/content/
overlay chrome://browser/content/browser.xul chrome://unityfox/content/main.xul
Это говорит браузеру загружать наш файл main.xul вместе с интерфейсом браузера browser.xul.

Тут стоит остановиться и вспомнить задачу, которую мы решаем. Наша цель — отобразить полосу загрузки и число активных загрузок в панели Unity. К счастью, Unity предоставляет специальный API для этого. Из этого API нам понадобятся функции:
  • UnityLauncherEntry *unity_launcher_entry_get_for_desktop_id (char *id);
  • void unity_launcher_entry_set_count (UnityLauncherEntry *self, gint64 count);
  • void unity_launcher_entry_set_count_visible (UnityLauncherEntry *self, gboolean visible);
  • void unity_launcher_entry_set_progress (UnityLauncherEntry *self, gdouble progress);
  • void unity_launcher_entry_set_progress_visible (UnityLauncherEntry *self, gboolean visible);
gint64 является аналогом long в си, а gboolean — аналогом int. Указатель на UnityLauncherEntry можно считать указателем на анонимную структуру и смело приводить к указателю на void. Дальнейшие комментарии пойдут прямо в коде main.xul.
<?xml version="1.0"?>
<overlay id="unityfox" 
         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
 
<script type="text/javascript">
 
// В расширениях Firefox принято осторожно относиться к глобальной области в xul
if ("undefined" == typeof(unityProgress)) {
 
var unityProgress = {
 
    setup: function() {
        // Загрузим модуль ctypes для обращения к системным библиотекам
        Components.utils.import("resource://gre/modules/ctypes.jsm");
 
        // Выполним загрузку библиотеки libunity.so.4
        // Нам не нужно, чтобы расширение генерировало ошибки при отсутствии libunity
        try {
              this.libunity = ctypes.open("libunity.so.4");
          } catch(err) { return; }
 
        // Опишем нужные функции libunity
        this.getEntry = this.libunity.declare("unity_launcher_entry_get_for_desktop_id", 
                                            ctypes.default_abi,
                                            ctypes.voidptr_t,
                                            ctypes.char.ptr);
 
 
        this.setProgress = this.libunity.declare("unity_launcher_entry_set_progress", 
                                                ctypes.default_abi,
                                                ctypes.void_t,
                                                ctypes.voidptr_t,
                                                ctypes.double);
 
        this.setVisibilityP = this.libunity.declare("unity_launcher_entry_set_progress_visible", 
                                                    ctypes.default_abi,
                                                    ctypes.void_t,
                                                    ctypes.voidptr_t,
                                                    ctypes.int);
 
        this.setVisibilityN = this.libunity.declare("unity_launcher_entry_set_count_visible", 
                                        ctypes.default_abi,
                                        ctypes.void_t,
                                        ctypes.voidptr_t,
                                        ctypes.int);
 
        this.setCount = this.libunity.declare("unity_launcher_entry_set_count", 
                                            ctypes.default_abi,
                                            ctypes.void_t,
                                            ctypes.voidptr_t,
                                            ctypes.long);
 
        // все основные функции библиотеки libunity работают с UnityLauncherEntry*
        this.entry = this.getEntry("firefox.desktop");
 
        this.Cc = Components.classes;
        this.Ci = Components.interfaces;
        this.IDLM = this.Ci.nsIDownloadManager;
 
        // Обновление панели будет происходить с помощью сервиса download-manager
        // Интерфейсы всех встроенных сервисов описаны на https://developer.mozilla.org
        this.dlMgr = this.Cc["@mozilla.org/download-manager;1"].getService(this.IDLM);
        this.dlMgr.addListener(this);
    },
 
    // Функция update вызывается на каждом событии от download-manager
    update: function() {
        var total = 0, cur = 0, count = 0;
        var dls = this.dlMgr.activeDownloads;
        while (dls.hasMoreElements()) {
            var dl = dls.getNext().QueryInterface(this.Ci.nsIDownload);
            // Пропускаем неактивные загрузки и загрузки, время окончания которых неизвестно
            if (dl.state != this.IDLM.DOWNLOAD_DOWNLOADING || dl.percentComplete == -1)
                continue;
 
            // Считаем общий размер, загруженный размер и число загрузок
            total += dl.size;
            cur += dl.amountTransferred;
            count++;
        }
 
        if (total == 0) {
            // Если нет загрузок, скрываем счётчик и полосу на панели
            this.setVisibilityP(this.entry, 0);
            this.setVisibilityN(this.entry, 0);
        } else {
            // Показываем ход загрузки (от 0 до 1) и число загрузок
            this.setProgress(this.entry, cur / total);
            this.setCount(this.entry, count);
            this.setVisibilityP(this.entry, 1);
            this.setVisibilityN(this.entry, 1);
        }
    },
 
    // Этот объект является nsIDownloadProgressListener по совместительству.
    // nsIDownloadManager будет отсылать сообщения нижеописанным функциям 
    onDownloadStateChange:     function() { this.update() },
    onStateChange:             function() { this.update() },
    onProgressChange:          function() { this.update() },
    onSecurityChange:          function() { this.update() }
};
}
 
// Наконец, инициализируем unityProgress через т. н. анонимное пространство имён
(function() {this.setup();}).apply(unityProgress);
</script>
</overlay>


Расширение готово. Теперь файлы из директории unityfox можно упаковать в zip-архив, изменить расширение файла на .xpi и установить перетаскиванием на окно браузера.

Готовое расширение и список ингредиентов можно найти по адресу addons.mozilla.org/ru/firefox/addon/unityfox. Приятного аппетита!

Текст этой статьи распространяется на условиях лицензии CC BY-SA 3.0
Tags:
Hubs:
+63
Comments 7
Comments Comments 7

Articles