Плазмоид (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 заметно увеличилось. Горячей плазмы!
Полный код плазмоида вы можете найти здесь.