
KDE4 Plasma Desktop. Создание плазмоида

Плазмоид (plasmoid) — это виджет рабочего стола в KDE4 Desktop. Любой видимый элемент управления на рабочем столе является плазмоидом, будь то часы, системный трей, монитор загруженности процессора или окошко с прогнозом погоды.
Этот урок описывает создание плазмоида, умеющего делать запросы к некоему серверу и показывать полученный результат. Так как сервер требует авторизации пользователя, будет разобран процесс хранения данных учетной записи пользователя в KWallet. Язык разработки: Python.
В качестве примера был написан плазмоид, проверяющий баланс сотового телефона одного иркутского оператора. Для получения данных необходимо авторизироваться в ИССА, достать из страницы данные и выйти из системы.
Пакет
Каждый плазмоид — это набор файлов, упакованных в zip архив. В этом уроке плазмоид будет называться «bwc-balance-plasmoid». Создадим одноименную директорию, в которой будут храниться все файлы проекта:
./contents/
./contents/code/
./contents/code/main.py
./metadata.desktop
metadata.desktop
metadata.desktop содержит все мета-данные о плазмоиде, например его название, автора или язык программирования, на котором он написан.
[Desktop Entry]
Encoding=UTF-8
Name=BWC Balance
Name[ru]=Баланс BWC
Type=Service
ServiceTypes=Plasma/Applet
Icon=phone
X-Plasma-API=python
X-Plasma-MainScript=code/main.py
X-KDE-PluginInfo-Author=SvartalF
X-KDE-PluginInfo-Email=self@svartalf.info
X-KDE-PluginInfo-Name=bwc-balance
X-KDE-PluginInfo-Version=0.1
X-KDE-PluginInfo-Website=http://bitbucket.org/svartalf/bwc-balance-plasmoid/
X-KDE-PluginInfo-Category=Online Services
X-KDE-PluginInfo-Depends=
X-KDE-PluginInfo-License=GPL
X-KDE-PluginInfo-EnabledByDefault=true
Назначение всех полей достаточно очевидно, поэтому не будем останавливаться здесь.
main.py
Основной код плазмоида находится в файле main.py.
Импортируем системные библиотеки, без использования которых создание виджета становится невозможным:
Copy Source | Copy HTML
- from PyQt4.QtCore import *
- from PyQt4.QtGui import *
- from PyKDE4.kio import *
- from PyKDE4.kdeui import *
- from PyKDE4.kdecore import *
- from PyKDE4.plasma import Plasma
- from PyKDE4 import plasmascript
- from PyKDE4.solid import Solid
Создаем класс плазмоида:
Copy Source | Copy HTML
- class BWCBalancePlasmoid(plasmascript.Applet):
- def __init__(self, parent, args=None):
- plasmascript.Applet.__init__(self, parent)
-
- def init(self):
- """Инициализация настроек"""
-
- # Плазмоид имеет пользовательские настройки
- self.setHasConfigurationInterface(True)
- self.setAspectRatioMode(Plasma.IgnoreAspectRatio)
-
- self.theme = Plasma.Svg(self)
- self.theme.setImagePath("widgets/background")
- self.setBackgroundHints(Plasma.Applet.DefaultBackground)
-
- # Расположение элементов плазмоида
- self.layout = QGraphicsLinearLayout(Qt.Horizontal, self.applet)
-
- # Label для выводимых данных
- self.label = Plasma.Label(self.applet)
- self.label.setText("0.0") # Изначально пусть будет 0
-
- # Добавляем Label к схеме расположения
- self.layout.addItem(self.label)
- self.applet.setLayout(self.layout)
-
- # И изменяем размеры плазмоида
- self.resize(50, 50)
После описания класса добавьте небольшую функцию, вызываемую Plasma для создания плазмоида:
Copy Source | Copy HTML
- def CreateApplet(parent):
- return BWCBalancePlasmoid(parent)
Для тестирования работы существует программа plasmoidviewer:
svartalf ~ $ plasmoidviewer bwc-balance-plasmoid
В результате получится такое красивое, но абсолютно не функциональное окошко.

Когда плазмоид будет готов, запакуйте его в zip архив.
Получившийся файл устанавливается в систему следующей командой:
plasmapkg -i bwc-balance-plasmoid.zip
и удаляется:
plasmapkg -r bwc-balance-plasmoid.zip
Настройки
Займемся реализацией пользовательских настроек.
В Qt Designer делаем диалог на основе QDialog с двумя полями ввода, и сохраняем его в settings_ui.ui

Полученный .ui файл конвертируем в .py файл. Это будет базовый диалог настроек.
pyuic4 settings_ui.ui -o settings_ui.py
Созданный диалог будет наследоваться нашей формой настроек. При инициализации формы будем пытаться заполнить поля уже имеющимися данными из KWallet.
Copy Source | Copy HTML
- class SettingsDialog(QWidget, Ui_SettingsDialog):
- def __init__(self, parent=None):
- QWidget.__init__(self)
-
- self.parent = parent
- self.setupUi(self)
-
- # Открываем локальный «бумажник»
- self.wallet = KWallet.Wallet.openWallet(KWallet.Wallet.LocalWallet(), 0)
- if self.wallet:
- # Выбираем нашу «папку» для хранения паролей
- self.wallet.setFolder("bwc-balance-plasmoid")
-
- if not self.wallet.entryList().isEmpty():
- phone = str(self.wallet.entryList().first())
- password = QString()
- # Считываем пароль для номера телефона
- self.wallet.readPassword(phone, password)
- # И заполняем этими данными поля диалога
- self.textPhone.setText(phone)
- self.textPassword.setText(str(password))
-
- def get_settings(self):
- return {"phone": str(self.textPhone.text()), "password": str(self.textPassword.text())}
Функция KWallet.Wallet.openWallet() принимает третий, необязательный параметр OpenType: синхронный/асинхронный режим открытия бумажника
При открытии в асинхронном режиме необходимо проверить возвращаемое значение функции, и если оно не равно None, подключить сигнал walletOpened() к слоту, который будет осуществлять работу с данными бумажника.
В этом случае бумажник открыт в синхронном режиме, из него считываются данные и заполняют поля диалога.
Теперь необходимо показывать этот диалог по просьбе пользователя и сохранять введенные данные.
Допишем у класса плазмоида в init() следующие строки:
Copy Source | Copy HTML
- # Здесь будет храниться объект диалога настроек
- self.settings_dialog = None
- # А это сами настройки
- self.settings = {"phone": None, "password": None}
-
- # При загрузке плазмоида открываем бумажник в асинхронном режиме
- # Так пользователь сразу увидит плазмоид и предложение разрешить доступ
- # этому приложению к бумажнику
- self.wallet = KWallet.Wallet.openWallet(KWallet.Wallet.LocalWallet(), 0, 1)
- if self.wallet:
- self.connect(self.wallet, SIGNAL("walletOpened(bool)"), self.walletOpened)
В функции self.walletOpened() открываем бумажник, считываем пользовательские данные и запускаем таймер, который будет через определенный промежуток времени обновлять информацию.

Добавим к классу BWCBalancePlasmoid функцию, которая будет вызываться при выборе соответствующего пункта меню.

Copy Source | Copy HTML
- def showConfigurationInterface(self):
- # Создаем объект нашего диалога
- self.settings_dialog = SettingsDialog(self)
-
- # Встраиваем его в стандартную форму-диалог
- dialog = KPageDialog()
- dialog.setFaceType(KPageDialog.Plain)
- dialog.setButtons(KDialog.ButtonCode(KDialog.Ok | KDialog.Cancel))
- page = dialog.addPage(self.settings_dialog, u"Настройки ИССА")
-
- # Соединяем слоты с сигналами
- self.connect(dialog, SIGNAL("okClicked()"), self.configAccepted)
- self.connect(dialog, SIGNAL("cancelClicked()"), self.configDenied)
-
- dialog.resize(350, 200)
- # Показываем диалог
- dialog.exec_()
Функции self.configAccepted() и self.ConfigDenied() будут вызываться при нажатии кнопок «Ok» и «Отмена» в диалоге. Так как при нажатии кнопки «Отмена» нам не нужно производить никаких действий, остается только описать логику configAccepted().
Copy Source | Copy HTML
- def configAccepted(self):
- # Обновляем данные в программе
- self.settings = self.settings_dialog.get_settings()
-
- # И сохраняем их в бумажник
- wallet = KWallet.Wallet.openWallet(KWallet.Wallet.LocalWallet(), 0)
- if wallet:
- if not wallet.hasFolder("bwc-balance-plasmoid"):
- wallet.createFolder("bwc-balance-plasmoid")
- wallet.setFolder("bwc-balance-plasmoid")
- for e in wallet.entryList():
- wallet.removeEntry(e)
- wallet.writePassword(self.settings["phone"], self.settings["password"])
После изменения настроек запускаем таймер обновления, и чтобы не ждать пока он сработает в первый раз, делаем загрузку данных.
Обновление данных
Условимся, что данные будут обновляться в раз в час. Прежде всего создадим таймер, по которому будет выполняться загрузка данных. Допишем к функции init() класса BWCBalancePlasmoid следующее:
Copy Source | Copy HTML
- self.timer = QTimer()
- self.connect(self.timer, SIGNAL("timeout(bool)"), self.loadBalance)
Здесь мы создаем объект класса QTimer() и подключаем его сигнал timeout() к функции loadBalance().
Остается только после загрузки плазмоида запустить таймер:
Copy Source | Copy HTML
- self.timer.start(1000*60*60) # Время указывается в миллисекундах
Загрузка данных
Изначально загрузка данных производилась через urllib2.build_opener() и urllib2.Request(), но у этого метода были следующие минусы:
- Загрузка субъективно занимала больше времени
- Производилась в синхронном режиме, поэтому в процессе работы плазмоид не отзывался на действия пользователя
Проблему решило использование сетевых функций KIO, а именно storedGet() и storedHttpPost(), которые работают в асинхронном режиме.
Я не буду подробно останавливаться на процессе работы с этими функциями, только приведу примеры GET и POST запросов:
GET
Copy Source | Copy HTML
- self.job = KIO.storedGet(KUrl("http://example.com/account"), KIO.Reload, KIO.HideProgressInfo)
- self.job.addMetaData("User-Agent", "User-Agent: bwc-balance-plasmoid")
-
- self.connect(self.job, SIGNAL("result(KJob*)"), self._get_result)
POST
Copy Source | Copy HTML
- self.job = KIO.storedHttpPost(QByteArray(urlencode({'phone': self.settings.get("phone"), 'password': self.settings.get("password")})), \
- KUrl("</code><code>http://example.com/</code><code>login"), KIO.HideProgressInfo)
- self.job.addMetaData("Content-type", "Content-Type: application/x-www-form-urlencoded")
- self.job.addMetaData("Accept", "Accept: text/plain")
- self.job.addMetaData("User-Agent", "User-Agent: bwc-balance-plasmoid")
-
- self.connect(self.job, SIGNAL("result(KJob*)"), self._get_result)
Остается только описать функцию self._get_result():
Copy Source | Copy HTML
- def self._get_result(self, job):
- print job.data() # Просто сделаем что-нибудь с полученным ответом
Итог
Был разобран процесс работы с настройками плазмоида, KWallet и сетевыми функциями KIO. Надеюсь, этого примера достаточно, чтобы в скором времени количество полезных плазмоидов для KDE Desktop заметно увеличилось. Горячей плазмы!
Полный код плазмоида вы можете найти здесь.
Comments 20
Only users with full accounts can post comments. Log in, please.