- Подписка на печатную версию:
- Подписка на электронную версию:
- Подшивки старых номеров журнала (печатные версии)
LXF110:KDE4
Материал из Linuxformat.
KDE4 |
---|
|
Поставщики данных
- ЧАСТЬ 3 Плазмоид из проекта прошлого месяца был статичным и только зря занимал место на рабочем столе. Чтобы быть по-настоящему полезным, апплет должен отображать постоянно меняющиеся сведения – этим сегодня и займется Андрей Боровский.
Идеология разделения данных и интерфейса, заметно изменившая структуру Qt 4, теперь овладела умами разработчиков KDE. Для расширений Plasma предлагается единый механизм доступа к данным – “data engines”, которые я буду называть «поставщиками данных». Получая данные из разных источников, поставщики передают их апплетам KDE Plasma (плазмоидам) с помощью унифицированного интерфейса. Следует сразу оговориться, что этот интерфейс до сих пор не принял законченную форму, так что в примерах к данной статье мы рассмотрим сразу несколько вариантов, которые следует применять в KDE 4.0.x и KDE 4.1.x (разумеется, ветка 4.0 уходит в прошлое, но на данный момент это может быть единственный работоспособный вариант KDE 4 для вашего дистрибутива). Будем надеяться, что интерфейсы libplasma, используемые в KDE 4.1.x, стабилизируются надолго.
Файл /proc/meminfo, как и все в директории /proc, является виртуальным. Он не хранится на диске, а формируется системой в ответ на наши запросы. Когда мы читаем этот файл, мы получаем данные о состоянии памяти на момент чтения.
Как отмечалось в предыдущей статье, информационные расширения Plasma используются в основном для отображения данных, меняющихся во времени – нагрузки на систему, сетевого трафика, прогнозов погоды и т.п. (хотя ничто не мешает использовать плазмоиды и для отображения статических данных, вряд ли пользователи захотят загромождать свои виртуальные рабочие столы такими апплетами). В принципе, для создания динамического плазмоида не обязательно использовать поставщики данных (мы ведь обошлись без них в прошлый раз, когда программировали статический плазмоид!). Можно организовать обновление с помощью таймера, однако использование поставщиков данных не только более удобно и создает меньшую нагрузку на систему, но и гарантирует нашему плазмоиду более долгую жизнь и широкое признание.
Подобно всем расширениям KDE, поставщики данных представляют собой разделяемые библиотеки, экспортирующие несколько классов. Каждый поставщик имеет один или несколько источников данных (data sources). Источники выдают данные в виде пар «ключ – значение», причем один источник может предоставлять несколько таких пар. Ключ – просто строка латинских символов, а значение может принадлежать самым разным типам (для его передачи используется QVariant). Все данные одного поставщика хранятся в глобальной хэш-таблице (контейнер QHash).
В качестве примера мы рассмотрим поставщик данных, предоставляющий заинтересованным расширениям Plasma информацию о состоянии оперативной памяти системы (ее мы почерпнем из файла /proc/meminfo). Данные о нагрузке на память – это как раз тот тип динамически меняющейся информации, для представления которой удобно использовать плазмоиды. В частности, наш поставщик данных позволит апплетам получать информацию об объеме свободной оперативной памяти и количестве выделенных буферов.
Пишем библиотеку поставщика
Основной класс поставщика данных должен быть потомком Plasma::DataEngine, который объявлен в заголовочном файле Plasma/DataEngine (или plasma/dataengine.h). Ниже приведен текст основного класса нашего поставщика данных, TestDataEngine:
class TestDataEngine : public Plasma::DataEngine { Q_OBJECT public: TestDataEngine(QObject* parent, const QVariantList& args); void init(); protected: bool sourceRequestEvent(const QString& name); bool updateSourceEvent(const QString& source); bool sourceRequested(const QString& name); bool updateSource(const QString& source); };
Ради обеспечения обратной совместимости класс TestDataEngine спроектирован по принципу «два в одном». Дело в том, что интерфейс класса Plasma::DataEngine претерпел при переходе от libplasma 1.0.x к libplasma 1.1 некоторые изменения. В обеих версиях библиотеки нам приходится перекрывать два виртуальных метода: один вызывается системой для создания источника данных, другой – для обновления данных уже созданного источника. В разных версиях библиотеки libplasma эти методы именуются по-разному. В старой версии для регистрации источников данных используется метод sourceRequested(), а для обновления – метод updateSource(). В новой версии для тех же целей вызываются методы sourceRequestEvent() и updateSourceEvent(). В классе TestDataEngine мы реализуем оба набора методов (благо они друг другу не мешают).
Кроме перечисленных четырех методов, у нашего класса есть еще конструктор и метод init(). Как следует из названия, система вызывает его для инициализации элементов поставщика данных, которые нельзя настроить в конструкторе. Перейдем к реализации класса:
TestDataEngine::TestDataEngine(QObject* parent, const QVariantList&args) : Plasma::DataEngine(parent) { setMinimumUpdateInterval(1000); } void TestDataEngine::init() { updateSource("MemFree"); updateSource("Buffers"); } bool TestDataEngine::sourceRequestEvent(const QString &name) { return updateSourceEvent(name); } bool TestDataEngine::sourceRequested(const QString &name) { return updateSourceEvent(name); } bool TestDataEngine::updateSource(const QString &name) { return updateSourceEvent(name); } bool TestDataEngine::updateSourceEvent(const QString &name) { QFile file("/proc/meminfo"); file.open(QIODevice::ReadOnly); char line[256]; while (file.readLine(line, 256) != 0) { QString s = line; if (s.indexOf(name) >= 0) { setData(name, s); file.close(); return true; } } file.close(); return false; } K_EXPORT_PLASMA_DATAENGINE(testde, TestDataEngine) #include "testde.moc"
Разбор исходного текста начнем с метода updateSourceEvent(), поскольку в нем сосредоточено все самое интересное. В единственном параметре этого метода система передает имя требуемого источника данных. Если метод updateSourceEvent() может обновить данные запрашиваемого источника, он возвращает значение true, в противном случае – false.
Данные о состоянии памяти в файле /proc/meminfo представлены в виде пар
ПАРАМЕТР=ЗНАЧЕНИЕ
Наш поставщик рассматривает каждый параметр как отдельный источник данных, благодаря чему плазмоиды могут получать значения только интересующих их величин, игнорируя все остальное. Таким образом, количество источников данных, предоставляемых нашим поставщиком, соответствует количеству параметров в файле /proc/meminfo (на практике мы не делаем доступными все возможные источники данных). Получив имя источника, наш метод updateSourceEvent() ищет в файле /proc/meminfo строку с соответствующим параметром. Если она найдена, метод возвращает значение true, иначе – false. Сами данные передаются вызывающему приложению с помощью метода setData() класса Plasma::DataEngine. Он существует в двух перегруженных вариантах. Первый принимает три параметра – имя источника данных, ключ и значение, второй – два (имя источника и значение). Имя источника и ключ передаются в строке QString, сами данные – в параметре типа QVariant.
Чтобы понять дальнейшее, нужно ясно представлять себе, что делает метод setData() – а он добавляет данные в глобальную таблицу. Если источник с указанным именем уже существует, связанные с ним данные обновляются. Если источника не существует, он будет создан. Далее апплет Plasma извлекает нужные данные из коллекции, используя имя источника. Таким образом, метод setData() используется как для обновления данных уже существующего источника, так и для создания нового. В нашем примере для регистрации источников данных и обновления используется один и тот же код, так что методы sourceRequestEvent(), sourceRequested(), updateSource() просто вызывают метод updateSourceEvent(). Напомню, что если вы пользуетесь библиотеками KDE 4.1.x или более поздними, вы можете удалить объявления и реализации методов sourceRequested() и updateSource().
Регистрация источников данных выполняется в методе init() (так, по крайней мере, требует KDE 4.0.x). В этом методе мы два раза вызываем метод updateSourceEvent(), один раз – для параметра MemFree (свободная память), другой раз – для Buffers. В результате мы создаем два источника данных, MemFree и Buffers, которые предоставляют доступ к значениям одноименных параметров файла /proc/meminfo (мы могли бы зарегистрировать больше источников, но для простоты примера ограничимся этими двумя).
В конструкторе класса мы вызываем метод setMinimumUpdateInterval(), который устанавливает минимальный интервал обновления данных поставщика (в миллисекундах). Это позволяет нам снизить нагрузку на систему, что особенно важно в тех случаях, когда получение данных связано с серьезными затратами ресурсов. Забегая вперед, отметим, что плазмоид может запрашивать данные и с более высокой частотой, но их фактическое обновление (вызов метода updateSourceEvent()) будет происходить не чаще, чем было указано с помощью setMinimumUpdateInterval().
Макрос K_EXPORT_PLASMA_DATAENGINE() спасает нас от необходимости рутинно объявлять вспомогательные классы, необходимые для экспорта главного класса поставщика данных. Его первый аргумент – имя поставщика, под которым он будет известен системе Plasma, второй – имя главного класса.
Подобно плазмоиду, поставщик данных нуждается в .desktop-файле. Вот как может выглядеть его простой (без интернационализации) вариант для нашего тестового поставщика:
[Desktop Entry] Name=Test Data Engine Comment=Test Data Engine Type=Service X-KDE-ServiceTypes=Plasma/DataEngine X-KDE-Library=plasma_engine_testde X-Plasma-EngineName=testde X-KDE-PluginInfo-Author=Andrei Borovsky X-KDE-PluginInfo-Email= X-KDE-PluginInfo-Name=testde X-KDE-PluginInfo-Version=0.1 X-KDE-PluginInfo-Website= X-KDE-PluginInfo-Category=Examples X-KDE-PluginInfo-Depends= X-KDE-PluginInfo-License=LGPL X-KDE-PluginInfo-EnabledByDefault=true
Параметру X-KDE-ServiceTypes присваивается значение Plasma/DataEngine (поставщик данных Plasma). X-KDE-Library устанавливается равным plasma_engine_testde (наш поставщик данных будет размещен в библиотеке plasma_engine_testde.so), для X-Plasma-EngineName (имя, под которым поставщик данных будет доступен Plasma) выбрано значение testde. Наконец, для X-KDE-PluginInfo-EnabledByDefault установлено значение true.
Нам осталось написать файл CMakeLists.txt для сборки нашего проекта. Вот он:
project(plasma-testde) set(CMAKE_INSTALL_PREFIX /usr/) find_package(Qt4 REQUIRED) find_package(KDE4 REQUIRED) include(KDE4Defaults) find_package(Plasma REQUIRED) add_definitions (${QT_DEFINITIONS} ${KDE4_DEFINITIONS}) include_directories(${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR} ${KDE4_INCLUDES} ) set(testde_engine_SRCS testde.cpp) kde4_add_plugin(plasma_engine_testde ${testde_engine_SRCS}) target_link_libraries(plasma_engine_testde ${KDE4_KDECORE_LIBS} ${PLASMA_LIBS}) install(TARGETS plasma_engine_testde DESTINATION ${PLUGIN_INSTALL_DIR}) install(FILES plasma-dataengine-testde.desktop DESTINATION ${SERVICES_INSTALL_DIR})
Как видно, он похож на мета-проект из предыдущей статьи, так что подробно останавливаться на его содержимом мы не будем. Отметим только необходимость правильно указывать директории для установки библиотеки поставщика. По умолчанию переменная PLUGIN_INSTALL_DIR, используемая в команде install(), содержит значение /lib/kde4/. Подобно этому, переменная SERVICES_INSTALL_DIR (которая указывает путь для установки .desktop-файла) содержит /share/kde4/services/. Если эти значения устраивают вашу систему, то все в порядке (для проверки выясните, существует ли в вашей системе директория /share/kde4/services/ и хранит ли она уже установленные .desktop-файлы). В моей системе KDE ищет файлы расширений в директориях с теми же именами, что заданы по умолчанию, но расположенных в /usr, поэтому в самом начале я меняю значение переменной CMAKE_INSTALL_PREFIX.
Прежде чем писать для нашего поставщика данных апплет Plasma, желательно убедиться, что он работает корректно (иначе мы не будем знать, где ошиблись – в поставщике или в плазмоиде). Для тестирования поставщика tested мы можем воспользоваться утилитой plasmaengineexplorer, которая играет в разработке поставщиков данных ту же роль, что и plasmoidviewer для плазмоидов.
После завершения сборки поставщика данных testde командуем
sudo make install
далее перезапускаем Plasma с помощью последовательности
kquitapp plasma plasma
и открываем утилиту plasmaengineexplorer (рис. 1).
Раскрывающийся список в верхней части окна утилиты содержит перечень установленных поставщиков данных. Если установка нашего поставщика прошла успешно, в этом списке должен быть пункт testde. В окне plasmaengineexplorer отображается список источников данных, предоставляемых выбранным поставщиком. В нашем случае это два источника данных, зарегистрированных в методе init(). Над окном с перечнем источников данных расположены строка ввода и наборный счетчик. Если вы хотите проверить, как выполняется обновление данных, укажите в строке ввода имя источника данных, а в наборном счетчике – интервал обновления, и щелкните кнопку Request.
Красивый интерфейс
Для окончательной победы над апплетами Plasma нам осталось написать плазмоид, использующий данные, предоставляемые поставщиком testde. Я назвал его meminfo, а полные исходные тексты можно найти в файле meminfo.tar.gz на LXFDVD. На момент написания этой статьи разработчики KDE еще не представили руководства по созданию плазмоидов такого типа, хотя wiki-затравка на сайте проекта висит уже не первый месяц. Описание, которое приводится ниже, составлено исключительно на основе исходных текстов плазмоидов, поставляемых вместе с KDE. Возможно, используемые здесь интерфейсы будут меняться со временем. Перед тем, как сесть писать собственный плазмоид, я советую вам изучить заголовочные файлы plasma/dataengine.h и plasma/dataenginemanager.h из последнего релиза KDE, а также исходные тексты плазмоидов из него же (апплеты и поставщики данных содержатся в пакетах kdebase-workspace).
Для подключения к поставщику данных необходимо создать экземпляр его класса. Мы делаем это с помощью метода dataEngine(), которому передается имя поставщика (альтернативный вариант – вызов метода loadDataEngine() объекта Plasma::DataEngineManager). В любом случае, вы получите указатель на объект класса Plasma::DataEngine, который на самом деле является объектом главного класса загруженного поставщика данных (напомню, что все они происходят от Plasma::DataEngine). В разных версиях KDE эти методы работают несколько по-разному, так что не бойтесь экспериментировать. Если для получения указателя на объект поставщика класса вы используете метод loadDataEngine(), то для повторного получения указателя на этот объект следует вызывать метод dataEngine() объекта Plasma::DataEngineManager. После того, как мы получили доступ к главному объекту поставщика данных, можно подключиться к нему, используя метод connectSource(). Вот как это делается в плазмоиде meminfo:
void MemInfo::init() { dataEngine("testde")->connectSource("MemFree", this, 1000); dataEngine("testde")->connectSource("Buffers", this, 1000); }
Мы подключаемся к обоим источникам данных, которые регистрирует поставщик. В первом аргументе метода connectSource() передается имя источника данных, во втором – указатель на объект плазмоида, выполняющий визуализацию данных (то есть на главный объект плазмоида, тот, что происходит от Plasma::Applet). В третьем параметре указывается интервал обновления данных в миллисекундах. В нашем примере плазмоид задает тот же интервал обновления, что и в поставщике данных, хотя это, напомню, необязательно. Что происходит в процессе вызова метода connectSource()? Главный объект загруженного поставщика данных связывает один из своих сигналов со слотом dataUpdated(), каковой мы должны реализовать в главном классе плазмоида. Напомню, что слоты в Qt могут вызваться по имени и списку аргументов, как и любые методы, помеченные макросом Q_INVOKABLE. Благодаря этому поставщик данных может вызывать слот, у которого нет прототипа в базовом классе Plasma::Applet. Заголовок слота dataUpdated() должен выглядеть так:
void dataUpdated(const QString&, const Plasma::DataEngine::Data&)
В первом аргументе слоту dataUpdated() передается имя источника обновляемых данных, во втором – ссылка на объект Plasma::DataEngine::Data, который содержит сами данные. Схему взаимодействия между плазмоидом и поставщиком данных можно отобразить графически (рис. 2).
Метод dataUpdated() в классе нашего апплета реализован просто:
void MemInfo::dataUpdated(const QString &name, const Plasma::DataEngine::Data &data){ m_mf = data.value("MemFree", m_mf).toString(); m_bs = data.value("Buffers", m_bs).toString(); update(); }
Мы извлекаем обновленные данные из контейнера Plasma::DataEngine::Data и помещаем их в переменные типа QString m_mf и m_bs, а затем вызываем метод update(), в результате чего апплет перерисовывает свое окно (метод paintInterface() выводит значения переменных m_mf и m_bs).
Остановимся подробнее на классе Plasma::DataEngine::Data. Он создан на основе ассоциативного контейнера QHash (LXF102). Для извлечения данных из контейнера мы используем метод value(). Первый аргумент – ключ (у нас – название источника данных), второй (необязательный) аргумент – переменная, значение которой должен вернуть метод value() в случае, если с заданным ключом не связаны никакие данные. Иначе говоря, в строке
m_mf = data.value("MemFree", m_mf).toString();
мы присваиваем переменной m_mf значение, связанное с ключом “MemFree”, если оно существует; в противном же случае сохраняем прежнее значение m_mf. Все это нужно постольку, поскольку при каждом вызове dataUpdate() методу передаются лишь те данные, обновление которых послужило его причиной, то есть данные только одного источника. С остальными элементами плазмоида мы уже знакомы. После применения CMake сборка апплета выполняется командой make, а установка – sudo make install (после этого следует перезапустить KDE). Мы можем посмотреть, как работает апплет, в окне plasmoidviewer или добавить его на рабочий стол с помощью утилиты Add Widgets (рис 3).
Плазмоиды, возможно, не самая увлекательная, но весьма оригинальная черта новой версии KDE. В следующий же раз мы займемся вещами не увлекательными и не оригинальными, но необходимыми – интернационализацией проектов KDE 4.