- Подписка на печатную версию:
- Подписка на электронную версию:
- Подшивки старых номеров журнала (печатные версии)
LXF112:KDE4
Материал из Linuxformat.
KDE4 |
---|
|
Содержание |
Солидная аппаратура
- ЧАСТЬ 5 Plasma, о которой мы много говорили на прошлых уроках – самая яркая, но далеко не единственная подсистема KDE4. В качестве финального аккорда Андрей Боровский припас для вас слой аппаратной абстракции под названием Solid.
Не так давно один известный в Рунете писатель задал сообществу программистов Linux вопрос: почему новые версии KDE все более и более громоздки и прожорливы? Ведь улучшение программы предполагает, в том числе, оптимизацию ее кода... Можно, конечно, ответить – мол, новые версии оболочки удовлетворяют растущим потребностям пользователей. Что, я полагаю, будет лукавством: не так уж сильно эти потребности и выросли. Вспомните, когда в последний раз менялся интерфейс самой популярной текстовой оболочки – Midnight Commander? Следует признать, что в стремлении к свободному творчеству, которое и привлекает многих программистов в мир открытого ПО, разработчики будут охотнее добавлять новые функции, нежели оптимизировать старые. Вот почему KDE постоянно растет и вширь, и вглубь, и, в частности, обзавелся системой управления устройствами Solid.
Зачем нужен Solid? Разумеется, его смысл и польза заключаются вовсе не в том, чтобы удовлетворять амбиции KDE-программистов. Как вы, конечно, знаете, KDE портируется на множество ОС, в том числе Windows и Mac OS X, и у каждой из них есть собственные средства управления оборудованием. Solid представляет собой универсальный способ взаимодействия с устройствами всюду, где работает KDE. В идеале, система Solid должна сократить количество непереносимого кода в вашем приложении (хотя свести его к нулю все равно не удастся). В этом и заключается ответ на вопрос о том, не лучше ли использовать HAL (на котором основана система Solid) напрямую. Linux HAL поддерживается не на всех платформах, которые намерен завоевать KDE. Кроме того, Linux HAL базируется на Dbus, и эффективная работа с ним предполагает хорошее знание этой своеобразной системы. В процессе же общения с Solid мы находимся в знакомой среде с сигналами, слотами и объектной моделью Qt/KDE.
Исследуем дерево устройств
Центральным классом Solid является Solid::Device (все классы Solid объявлены в пространстве имен Solid), который представляет устройство. Дать четкое определение того, что является устройством с точки зрения Solid, не так-то просто. Это и процессор, и его загружаемый микрокод, и видеокамера, и ее аудиоподсистема, оптический привод и диск в нем, винчестер и каждый его раздел... Если такая модель кажется вам запутанной, вспомните, что в других системах дело обстоит не лучше (в Диспетчер оборудования Windows давно заглядывали?). Устройства Solid образуют иерархию, корнем которой является, конечно, Computer. Чтобы еще больше вас запутать – каждое устройство предоставляет один или несколько интерфейсов, представляемых потомками класса Solid::DeviceInterface.
Пока вы еще не успели разочароваться в Solid, поспешим уточнить, что может и чего не может эта система. Solid позволяет получать различные сведения об устройствах, в том числе отслеживать их состояние в режиме реального времени. При этом Solid не предоставляет собственных средств для обмена данными с устройствами. С помощью Solid вы можете получить имя файла, соответствующего устройству, его идентификатор или другую информацию, необходимую для того, чтобы наладить обмен информацией, но его придется выполнять с помощью интерфейса, предоставляемого операционной системой. Например, все известные Solid устройства, способные записывать и воспроизводить аудиоданные, предоставляют интерфейс Solid::AudioInterface. У него есть метод driverHandle(), который возвращает значение типа QVariant. Для устройств, которые обслуживают драйверы ALSA, фактическим значением driverHandle() будет запись, содержащая имя звуковой карты (в системе ALSA) и номера первичного и вторичного устройств. Если устройство работает под управлением драйвера OSS, метод driverHandle() вернет строку с именем файла устройства.
Практическое знакомство с Solid мы начнем с программы обзора всех устройств, присутствующих в системе. Приложение devtree, чьи исходные тексты вы найдете на диске, покажет вам полный список устройств с учетом их иерархии (рис. 1).
Доступ к полному списку устройств, поддерживаемых Solid, можно получить с помощью статического метода Solid::Device::allDevices(). Он возвращает нам объект типа QList<Solid::Device>. Простая конструкция
foreach (const Solid::Device &device, Solid::Device::allDevices()) { ... }
позволит перебрать все элементы данного списка (роль итератора играет переменная device). Однако последовательный перебор перечня устройств не так уж и интересен. Дело в том, что расположение устройств в списке Solid::Device::allDevices() не учитывает иерархических отношений между ними. В программе devtree мы используем рекурсивный метод заполнения виджета QTreeWidget элементами списка (он не самый эффективный, но самый простой – альтернативу рассмотрим в конце статьи):
void devtreeView::enumDevices() { ui_devtreeview_base.treeWidget->setColumnCount(3); QStringList hsl; hsl << "UDI" << "Vendor" << "Name"; ui_devtreeview_base.treeWidget->setHeaderLabels(hsl); QTreeWidgetItem * root = NULL; foreach (const Solid::Device &device, Solid::Device::allDevices()) { QStringList sl; if(device.parentUdi() == "") { sl << device.udi() << device.vendor() << device.product(); root = new QTreeWidgetItem(ui_devtreeview_base.treeWidget, sl); break; } } findChildren(root); } void devtreeView::findChildren(QTreeWidgetItem * root) { foreach (const Solid::Device &device, Solid::Device::allDevices()) { if(device.parentUdi() == root->text(0)) { QStringList sl; sl << device.udi() << device.vendor() << device.product(); findChildren(new QTreeWidgetItem(root, sl)); } } }
Один из наиболее часто используемых методов класса Solid::Device, udi(), возвращает уникальный идентификатор устройства. В Linux, где Solid основан на FreeDesktop HAL, идентификатором устройства служит HAL-адрес устройства (строка вида “/org/freedestop/Hal/...”). Нам не обязательно разбираться в том, как формируются эти адреса: важно знать, что именно они идентифицируют устройства. Метод parentUdi() класса Solid::Device возвращает идентификатор родительского устройства, а соответствующий ему объект класса Solid::Device можно получить с помощью метода Solid::Device::parent(). Для корневого устройства метод parentUdi() возвращает пустую строку, чем мы и пользуемся в методе enumDevices(). После того как корневое устройство найдено, мы вызываем рекурсивно метод findChildren(), который добавляет в виджет ui_devtreeview_base.treeWidget устройства, являющиеся непосредственными потомками корневого. Для каждого найденного устройства наша программа отображает три элемента данных: идентификатор устройства, наименование изготовителя (возвращается методом Solid::Device::vendor()) и описание устройства (возвращается методом product()). У класса Solid::Device есть еще и метод icon(), который возвращает строку с именем пиктограммы устройства (пока что непустая строка возвращается только для корневого устройства Computer).
Если бы система Solid позволяла только перечислить имеющиеся в наличии устройства, пользы от нее было бы не очень много. Однако основная задача Solid заключается в том, что-бы информировать систему о различных событиях, связанных с устройствами. Делается это, естественно, с помощью сигналов и слотов. Сигналы, оповещающие систему о событиях, связанных с устройствами, эмитируются объектами специальных классов. На момент написания статьи их существовало три: Solid::DeviceNotifier, Solid::Networking::Notifier и Solid::PowerManagement::Notifier. Объект класса Solid::DeviceNotifier информирует программу о подключении и отключении устройств. Объект класса Solid::Networking::Notifier сообщает о подключении и отключении системы от сети, а объект класса Solid::PowerManagement::Notifier позволяет программе отслеживать режим работы системы питания. Особняком стоит класс WebcamWatcher, который выполняет функции Solid::DeviceNotifier исключительно для web-камер.
На первый взгляд может показаться, что возможности Solid в плане оповещения программ о событиях устройств крайне скромны, но это не так. Помните, что устройствами в Solid считаются самые разные вещи. Объект класса Solid::DeviceNotifier может оповещать программу о таких событиях, как установка нового CD-диска в привод, подключение USB-устройства или монтирование нового раздела файловой системы.
Вполне логично задействовать объект класса Solid::DeviceNotifier в программе devtree для обновления дерева устройств в случае изменения оного. Система Solid предоставляет каждой программе один объект класса Solid::DeviceNotifier. Для получения указателя на него мы воспользуемся статическим методом Solid::DeviceNotifier::instance():
Solid::DeviceNotifier * dn = Solid::DeviceNotifier::instance();
Объект класса Solid::DeviceNotifier эмитирует два сигнала: deviceAdded() (добавлено новое устройство) и deviceRemoved() (устройство отключено). В качестве аргумента оба сигнала передают строку с идентификатором устройства. Мы связываем оба сигнала со слотом update(), который мы добавили в главный класс нашей программы:
dn->connect(dn, SIGNAL(deviceAdded(const QString)), this, SLOT(update())); dn->connect(dn, SIGNAL(deviceRemoved(const QString)), this, SLOT(update()));
Слот update() не обрабатывает переданный ему параметр, а просто очищает виджет ui_devtreeview_base.treeWidget и заново строит дерево устройств. Разумеется, такой подход нельзя назвать самым эффективным. Для ускорения работы программы можно было бы найти для добавленного/удаленного родительское устройство и перестроить только соответствующую ему часть дерева.
Для сборки программы devtree в файл CMakeLists.txt следует добавить данные о заголовочных файлах и библиотеках Solid. Вопреки тому, что говорит документация, все заголовочные файлы Solid API хранятся в поддиректории solid стандартной директории include. Переменная CMake KDE4_INCLUDE_DIR уже содержит ее имя. Единственное изменение, которое нужно внести в файл CMakeLists.txt по сравнению со стандартным файлом приложения KDE 4 – это добавить переменную KDE4_SOLID_LIBS в качестве аргумента команды target_link_libraries(). Теперь программу devtree можно компилировать.
Интерфейсы устройств
В модели Solid объекты класса Solid::Device соответствуют общему понятию «устройство» и позволяют непосредственно получить только те данные, которые имеют смысл для всех устройств, независимо от их типа. Более подробную информацию о каждом устройстве можно извлечь с помощью интерфейсов, которые представлены в Solid объектами класса Solid::DeviceInterface и его потомков. Каждое устройство (то есть корректно созданный объект Solid::Device) предоставляет как минимум один интерфейс. Узнать, поддерживает ли устройство определенный интерфейс, можно с помощью метода isDeviceInterface() класса Solid::Device. Его аргументом должно быть значение типа Solid::DeviceInterface::Type, указывающее тип требуемого интерфейса. Полный список типов интерфейсов можно найти в документации Solid и файле solid/deviceinterface.h. Например, тип Processor определяет интерфейс ЦП, Block – блочное устройство, StorageVolume – раздел на диске, Battery – аккумулятор. Учитывая структуру Solid, вы не должны удивляться, что среди поддерживаемых типов интерфейсов одновременно присутствуют интерфейсы логических и физических устройств. Если объект Solid::Device поддерживает запрошенный интерфейс, метод isDeviceInterface() возвращает значение true.
После того как вы выяснили, поддерживает ли устройство некоторый интерфейс, вы, скорее всего, захотите получить объект, его реализующий. Сделать это можно с помощью метода Solid::Device:
- asDeviceInterface(), аргументом которого выступает тип требуемого
интерфейса. В случае успеха метод возвращает указатель на объект класса, производного от Solid::DeviceInterface, соответствующего запрошенному интерфейсу. Имейте в виду, что не для каждого значения Solid::DeviceInterface::Type существует свой класс (так, по крайней мере, обстояло дело на момент написания статьи).
В приложениях, использующих Solid, довольно часто требуется получить список устройств, поддерживающих определенный интерфейс (вполне логично, что программа, которая обрабатывает изображение, поступающее с видеокамер, захочет иметь полный список устройств, поддерживающих интерфейс Camera, а не вообще всех устройств). Для этого можно было бы, конечно, перебрать полный список устройств системы (как мы делали выше), проверяя, поддерживает ли каждое из них требуемый интерфейс, однако Solid предоставляет нам более простой и эффективный способ. Класс Solid::Device экспортирует несколько статических методов, предназначенных для управления не конкретным устройством, а всем списком устройств Solid (с одним из них, allDevices(), мы уже знакомы). Статические методы Solid::Device::listFromType() и Solid::Device::listFromQuery() позволяют получить подмножество списка устройств, заданное специальными признаками. Нас интересует метод listFromType(), который возвращает список устройств, поддерживающих определенный интерфейс (говоря «устройство», я, разумеется, имею в виду объект класса Solid::Device). Вот как, например, мы можем получить список устройств, поддерживающих интерфейс NetworkInterface:
QList<Solid::Device> devlist = Solid::Device::listFromType(Solid::DeviceInterface::NetworkInterface, QString());
Если во втором параметре listFromType() передать идентификатор устройства, в результирующий список войдут только сетевые устройства, являющиеся потомками заданного (пустая строка, разумеется, означает, что нас интересуют все сетевые устройства).
Solid и Plasma
Работу с интерфейсами устройств мы продемонстрируем на примере динамического плазмоида. Как уже говорилось в LXF110, плазмоиды хорошо подходят для того, чтобы информировать пользователя о важных событиях, происходящих в системе. Solid, как мы уже видели, может служить источником такого рода событий.
Для передачи данных от Solid плазмоиду мы воспользуемся стандартным механизмом поставщиков данных Plasma (LXF110). Наш плазмоид будет информировать пользователя о состоянии сетевых интерфейсов системы (рис. 2).
Полные исходные тексты поставщика данных (архив networkengine) и плазмоида ncview вы найдете на LXFDVD. Networkengine предоставляет один источник по имени NCs. Данные представляют собой массив строк (QStringList), в котором содержится перечень сетевых устройств, имена соответствующих сетевых интерфейсов Linux и MAC-адреса устройств (это, собственно говоря, вся информация, которую можно извлечь из интерфейса NetworkInterface). Рассмотрим объявление главного класса поставщика данных NetStatEngine:
class NetStatEngine : public Plasma::DataEngine { Q_OBJECT public: NetStatEngine(QObject* parent, const QVariantList&); protected: bool sourceRequestEvent(const QString& name); bool updateSourceEvent(const QString& source); private slots: void updateDevices(); };
Основные элементы этого класса должны быть знакомы вам по LXF110 ( в отличие от той статьи, здесь мы используем только синтаксис KDE 4.1.x: пришла пора обновиться, если вы до сих пор этого не сделали). Новый элемент здесь – только слот updateDevices(). В конструкторе класса мы вызываем метод updateSourceEvent(), как и в любом поставщике данных, а затем получаем указатель на глобальный объект Solid::DeviceNotifier:
NetStatEngine::NetStatEngine(QObject* parent, const QVariantList&) : Plasma::DataEngine(parent) { updateSourceEvent("NCs"); Solid::DeviceNotifier * dn = Solid::DeviceNotifier::instance(); dn->connect(dn, SIGNAL(deviceAdded(const QString)), this, SLOT(updateDevices())); dn->connect(dn, SIGNAL(deviceRemoved(const QString)), this, SLOT(updateDevices())); }
Как и в рассмотренном выше случае, этот объект нужен нам для того, чтобы связать слот updateDevices() с сигналами deviceAdded() и deviceRemoved(). Рабочая лошадка нашего поставщика данных – метод updateSourceEvent():
bool NetStatEngine::updateSourceEvent(const QString &name) { if (name == "NCs") { QList<Solid::Device> devlist = Solid::Device::listFromType(Solid::DeviceInterface::NetworkInterface, QString()); QStringList sl; foreach (const Solid::Device &device, devlist) { sl << "Device: " + device.parent().product(); Solid::NetworkInterface * ni = (Solid::NetworkInterface *) device.as DeviceInterface(Solid::DeviceInterface::NetworkInterface); sl << "Interface: " + ni->ifaceName(); sl << "Address: " + ni->hwAddress(); sl << QString("Type: ") + (ni->isWireless() ? "Wireless" : "Wired"); } setData(name, sl); return true; } return false; }
Наша первая задача – получить список устройств, поддерживающих интерфейс NetworkInterface, как было описано выше. Далее мы перебираем все элементы списка сетевых устройств и для каждого из них получаем указатель на объект класса Solid::NetworkInterface посредством метода asDeviceInterface(). Данные, полученные с помощью интерфейсов, заносятся в список QStringList, который передается методу setData(). Метод ifaceName() класса Solid::NetworkInterface возвращает строку с именем интерфейса (“eth0”, “eth1” и т.д.). С помощью метода hwAddress() мы можем получить MAC-адрес устройства в текстовом виде (метод macAddress() возвращает MAC-адрес в виде числа). Метод isWireless() вернет значение true, если сетевой интерфейс является беспроводным, и false – в противном случае. Чтобы сделать список сетевых устройств более информативным, мы выводим название каждого устройства. Его можно получить с помощью метода product() класса Solid::Device; однако для всех устройств, соответствующих сетевым интерфейсам, он возвращает строку “Network Interface”, что, конечно, не очень интересно. Для получения имени физического устройства, предоставляющего сетевой интерфейс, мы вызываем метод product() устройства, являющегося «родителем» сетевого интерфейса.
Нам осталось рассмотреть вопрос о том, как поставщик данных оповещает плазмоид о подключении и отключении сетевых интерфейсов (в LXF110, напомним, это происходило в результате периодического опроса). Для такого нечастого события, как добавление и удаление сетевых интерфейсов, регулярный опрос, выполняемый плазмоидом, явно избыточен. Для нерегулярных и нечастых событий лучше подходит модель, при которой поставщик сам информирует заинтересованный плазмоид об изменении данных. Наладить этот механизм очень просто. Ниже приводится исходный текст слота updateDevices(), который обрабатывает сигналы deviceAdded() и deviceRemoved():
void NetStatEngine::updateDevices() { updateSourceEvent("NCs"); }
Не удивляйтесь – это действительно все, что необходимо для оповещения плазмоида. Метод updateSourceEvent() вызывает метод setData(), который с помощью соответствующего сигнала активирует слот dataUpdated() в плазмоиде.
Наконец, зная про метод listFromType(), мы можем написать улучшенный вариант метода findChildren() для программы devtree:
void devtreeView::findChildren(QTreeWidgetItem * root) { QList<Solid::Device> children = Solid::Device::listFromType(Solid::DeviceInterface::GenericInterface, root->text(0)); foreach (const Solid::Device &device, children) { QStringList sl; sl << device.udi() << device.vendor() << device.product(); findChildren(new QTreeWidgetItem(root, sl)); } }
Значение Solid::DeviceInterface::GenericInterface соответствует интерфейсу базового типа, который поддерживается всеми устройствами.
На тему программирования для KDE 4 можно было бы написать еще очень много, но мы завершаем наш обзор. Того, что вы узнали о новой версии KDE из этих уроков, вполне достаточно, чтобы написать расширение KDE, которое, возможно, даже оправдает утяжеление будущего дистрибутива на несколько мегабайт. Не забудьте сообщить нам, если получится что-то стоящее!