Pull to refresh

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

Reading time 10 min
Views 10K
Plasma
Плазмоид (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
  1. from PyQt4.QtCore import *
  2. from PyQt4.QtGui import *
  3. from PyKDE4.kio import *
  4. from PyKDE4.kdeui import *
  5. from PyKDE4.kdecore import *
  6. from PyKDE4.plasma import Plasma
  7. from PyKDE4 import plasmascript
  8. from PyKDE4.solid import Solid


Создаем класс плазмоида:
Copy Source | Copy HTML
  1. class BWCBalancePlasmoid(plasmascript.Applet):
  2.     def __init__(self, parent, args=None):
  3.         plasmascript.Applet.__init__(self, parent)
  4.  
  5.     def init(self):
  6.         """Инициализация настроек"""
  7.  
  8.         # Плазмоид имеет пользовательские настройки
  9.         self.setHasConfigurationInterface(True)
  10.         self.setAspectRatioMode(Plasma.IgnoreAspectRatio)
  11.  
  12.         self.theme = Plasma.Svg(self)
  13.         self.theme.setImagePath("widgets/background")
  14.         self.setBackgroundHints(Plasma.Applet.DefaultBackground)
  15.  
  16.         # Расположение элементов плазмоида
  17.         self.layout = QGraphicsLinearLayout(Qt.Horizontal, self.applet)
  18.  
  19.         # Label для выводимых данных
  20.         self.label = Plasma.Label(self.applet)
  21.         self.label.setText("0.0") # Изначально пусть будет 0
  22.  
  23.         # Добавляем Label к схеме расположения
  24.         self.layout.addItem(self.label)
  25.         self.applet.setLayout(self.layout)
  26.  
  27.         # И изменяем размеры плазмоида
  28.         self.resize(50, 50)


После описания класса добавьте небольшую функцию, вызываемую Plasma для создания плазмоида:
Copy Source | Copy HTML
  1. def CreateApplet(parent):
  2.     return BWCBalancePlasmoid(parent)


Для тестирования работы существует программа plasmoidviewer:
svartalf ~ $ plasmoidviewer bwc-balance-plasmoid
В результате получится такое красивое, но абсолютно не функциональное окошко.

Plasmoid basic view

Когда плазмоид будет готов, запакуйте его в zip архив.

Получившийся файл устанавливается в систему следующей командой:
plasmapkg -i bwc-balance-plasmoid.zip
и удаляется:
plasmapkg -r bwc-balance-plasmoid.zip

Настройки



Займемся реализацией пользовательских настроек.

В Qt Designer делаем диалог на основе QDialog с двумя полями ввода, и сохраняем его в settings_ui.ui

QT Designer

Полученный .ui файл конвертируем в .py файл. Это будет базовый диалог настроек.

pyuic4 settings_ui.ui -o settings_ui.py

Созданный диалог будет наследоваться нашей формой настроек. При инициализации формы будем пытаться заполнить поля уже имеющимися данными из KWallet.

Copy Source | Copy HTML
  1. class SettingsDialog(QWidget, Ui_SettingsDialog):
  2.     def __init__(self, parent=None):
  3.         QWidget.__init__(self)
  4.  
  5.         self.parent = parent
  6.         self.setupUi(self)
  7.  
  8.         # Открываем локальный «бумажник»
  9.         self.wallet = KWallet.Wallet.openWallet(KWallet.Wallet.LocalWallet(),  0)
  10.         if self.wallet:
  11.             # Выбираем нашу «папку» для хранения паролей
  12.             self.wallet.setFolder("bwc-balance-plasmoid")
  13.  
  14.             if not self.wallet.entryList().isEmpty():
  15.                 phone = str(self.wallet.entryList().first())
  16.                 password = QString()
  17.                 # Считываем пароль для номера телефона
  18.                 self.wallet.readPassword(phone, password)
  19.                 # И заполняем этими данными поля диалога
  20.                 self.textPhone.setText(phone)
  21.                 self.textPassword.setText(str(password))
  22.  
  23.     def get_settings(self):
  24.         return {"phone": str(self.textPhone.text()), "password": str(self.textPassword.text())}


Функция KWallet.Wallet.openWallet() принимает третий, необязательный параметр OpenType: синхронный/асинхронный режим открытия бумажника

При открытии в асинхронном режиме необходимо проверить возвращаемое значение функции, и если оно не равно None, подключить сигнал walletOpened() к слоту, который будет осуществлять работу с данными бумажника.

В этом случае бумажник открыт в синхронном режиме, из него считываются данные и заполняют поля диалога.

Теперь необходимо показывать этот диалог по просьбе пользователя и сохранять введенные данные.

Допишем у класса плазмоида в init() следующие строки:
Copy Source | Copy HTML
  1. # Здесь будет храниться объект диалога настроек
  2. self.settings_dialog = None
  3. # А это сами настройки
  4. self.settings = {"phone": None, "password": None}
  5.  
  6. # При загрузке плазмоида открываем бумажник в асинхронном режиме
  7. # Так пользователь сразу увидит плазмоид и предложение разрешить доступ
  8. # этому приложению к бумажнику
  9. self.wallet = KWallet.Wallet.openWallet(KWallet.Wallet.LocalWallet(),  0, 1)
  10. if self.wallet:
  11.     self.connect(self.wallet, SIGNAL("walletOpened(bool)"), self.walletOpened)


В функции self.walletOpened() открываем бумажник, считываем пользовательские данные и запускаем таймер, который будет через определенный промежуток времени обновлять информацию.

Wallet request

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

Plasmoid call settings
Copy Source | Copy HTML
  1. def showConfigurationInterface(self):
  2.     # Создаем объект нашего диалога
  3.     self.settings_dialog = SettingsDialog(self)
  4.  
  5.     # Встраиваем его в стандартную форму-диалог
  6.     dialog = KPageDialog()
  7.     dialog.setFaceType(KPageDialog.Plain)
  8.     dialog.setButtons(KDialog.ButtonCode(KDialog.Ok | KDialog.Cancel))
  9.     page = dialog.addPage(self.settings_dialog, u"Настройки ИССА")
  10.  
  11.     # Соединяем слоты с сигналами
  12.     self.connect(dialog, SIGNAL("okClicked()"), self.configAccepted)
  13.     self.connect(dialog, SIGNAL("cancelClicked()"), self.configDenied)
  14.  
  15.     dialog.resize(350, 200)
  16.     # Показываем диалог
  17.     dialog.exec_()


Функции self.configAccepted() и self.ConfigDenied() будут вызываться при нажатии кнопок «Ok» и «Отмена» в диалоге. Так как при нажатии кнопки «Отмена» нам не нужно производить никаких действий, остается только описать логику configAccepted().

Copy Source | Copy HTML
  1. def configAccepted(self):
  2.     # Обновляем данные в программе
  3.     self.settings = self.settings_dialog.get_settings()
  4.  
  5.     # И сохраняем их в бумажник
  6.     wallet = KWallet.Wallet.openWallet(KWallet.Wallet.LocalWallet(),  0)
  7.     if wallet:
  8.         if not wallet.hasFolder("bwc-balance-plasmoid"):
  9.             wallet.createFolder("bwc-balance-plasmoid")
  10.         wallet.setFolder("bwc-balance-plasmoid")
  11.         for e in wallet.entryList():
  12.             wallet.removeEntry(e)
  13.         wallet.writePassword(self.settings["phone"], self.settings["password"])


После изменения настроек запускаем таймер обновления, и чтобы не ждать пока он сработает в первый раз, делаем загрузку данных.

Обновление данных



Условимся, что данные будут обновляться в раз в час. Прежде всего создадим таймер, по которому будет выполняться загрузка данных. Допишем к функции init() класса BWCBalancePlasmoid следующее:
Copy Source | Copy HTML
  1. self.timer = QTimer()
  2. self.connect(self.timer, SIGNAL("timeout(bool)"), self.loadBalance)


Здесь мы создаем объект класса QTimer() и подключаем его сигнал timeout() к функции loadBalance().

Остается только после загрузки плазмоида запустить таймер:
Copy Source | Copy HTML
  1. self.timer.start(1000*60*60) # Время указывается в миллисекундах 


Загрузка данных



Изначально загрузка данных производилась через urllib2.build_opener() и urllib2.Request(), но у этого метода были следующие минусы:
  • Загрузка субъективно занимала больше времени
  • Производилась в синхронном режиме, поэтому в процессе работы плазмоид не отзывался на действия пользователя

Проблему решило использование сетевых функций KIO, а именно storedGet() и storedHttpPost(), которые работают в асинхронном режиме.

Я не буду подробно останавливаться на процессе работы с этими функциями, только приведу примеры GET и POST запросов:

GET

Copy Source | Copy HTML
  1. self.job = KIO.storedGet(KUrl("http://example.com/account"), KIO.Reload, KIO.HideProgressInfo)
  2. self.job.addMetaData("User-Agent", "User-Agent: bwc-balance-plasmoid")
  3.  
  4. self.connect(self.job, SIGNAL("result(KJob*)"), self._get_result)


POST

Copy Source | Copy HTML
  1. self.job = KIO.storedHttpPost(QByteArray(urlencode({'phone': self.settings.get("phone"), 'password': self.settings.get("password")})), \
  2.     KUrl("</code><code>http://example.com/</code><code>login"), KIO.HideProgressInfo)
  3. self.job.addMetaData("Content-type", "Content-Type: application/x-www-form-urlencoded")
  4. self.job.addMetaData("Accept", "Accept: text/plain")
  5. self.job.addMetaData("User-Agent", "User-Agent: bwc-balance-plasmoid")
  6.  
  7. self.connect(self.job, SIGNAL("result(KJob*)"), self._get_result)


Остается только описать функцию self._get_result():
Copy Source | Copy HTML
  1. def self._get_result(self, job):
  2.     print job.data() # Просто сделаем что-нибудь с полученным ответом 


Итог



Был разобран процесс работы с настройками плазмоида, KWallet и сетевыми функциями KIO. Надеюсь, этого примера достаточно, чтобы в скором времени количество полезных плазмоидов для KDE Desktop заметно увеличилось. Горячей плазмы!

Полный код плазмоида вы можете найти здесь.
Tags:
Hubs:
+52
Comments 20
Comments Comments 20

Articles