Журнал LinuxFormat - перейти на главную

LXF112:KDE4

Материал из Linuxformat
(Различия между версиями)
Перейти к: навигация, поиск
м (Новая: {{Цикл/KDE4}} == Солидная аппаратура == : ''ЧАСТЬ 5 Plasma, о которой мы много говорили на прошлых уроках – самая ...)

Версия 16:33, 4 октября 2009

Солидная аппаратура

ЧАСТЬ 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::DeviceNotif ier::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, которое, возможно, даже оправдает утяжеление будущего дистрибутива на несколько мегабайт. Не забудьте сообщить нам, если получится что-то стоящее!

Персональные инструменты
купить
подписаться
Яндекс.Метрика