Пользователь
0,0
рейтинг
27 ноября 2009 в 10:28

Разработка → KDE4 Plasma Desktop. Создание плазмоида

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

Полный код плазмоида вы можете найти здесь.
svartalf @svartalf
карма
67,6
рейтинг 0,0

Похожие публикации

Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

Комментарии (20)

  • 0
    Я как-то писал бинарный плазмоид — инетересно было посмотреть, как дело обстоит со скриптами. Спасибо.
    • 0
      впринципе все тоже самое) сравниваю этот псто и плазмоид, который я делал когда-то (тоже бумажник и сеть, только я ее делал через кутешные методы, а не через кдешные). И подобное единообразие не может не радовать.
      • 0
        Единообразие — это хорошо, да.
  • НЛО прилетело и опубликовало эту надпись здесь
    • 0
      О, да! Плазма девелопмент в связи с этим часто становится крайне «увлекательным» занятием. :)
      • НЛО прилетело и опубликовало эту надпись здесь
        • 0
          да, у меня она теперь обычно просто не запускается
  • 0
    О, настройки добавлю в пару своих, спасибо!
  • 0
    Спасибо, скоро попробую)
  • 0
    Полезная статейка и все на питоне, не думал :) как-то руки не доходили поковыряться.
    Вот только один минус в новых кедах с этим плазмоидом, что десктоп немного не юзабельный. папки и иконки в отдельном окне. Привык, что в разных углах рабочего стола находятся разные по важности папки, а тут не получится так сделать. Только если накидать этих виджетов… Надо будет посмотреть на новые версии кде :), пользовался старыми т.к. новые мне не очень пришлись по душе в плане юзабилити.

    Хорошая работа, автор!
    • 0
      Начиная с KDE 4.2 можно этот виджет растянуть на весь стол.
    • +1
      что не получится сделать? поверьте, десктоп очень даже юзабельный и удобный. я вполне себе раскидал по разным частям разные по логике папки. вот только они у меня точно не перемешаются при случайном нажатии «упорядочить значки по». и если не влезут, то все-равно будут сгруппированы и с ненавязчивым скроллом.
      Free Image Hosting at FunkyIMG.com
      • 0
        7 лет эволюции:

        image

        P.S да, я не любитель виджетов image
  • 0
    извиняюсь, за оффтоп, но что использовать для подобного класса виджетов в Гноме или вообще в ДЕ независимой среде?
    • 0
      Для Gnome это Gnome Applets, и если мы говорим о Python, то вам понадобятся pygtk и gnomeapplet
      • 0
        а апплеты живут где-нибудь кроме панелей?
        • 0
          Насколько я знаю, нет
  • 0
    Огромная просьба: добавьте в ваш плазмоид ещё 2 поля настройки: УРЛ и регулярное выражение. Выложите его куда-нибудь.

    И вы решите проблему проверки баланса у всех пользователей KDE.

    Спасибо!

    P.S. Не забудьте дать ссылку мне :-)
  • 0
    Я думаю периодичность обновления (в минутах) тоже можно загнать в настройки.

    Ещё полезно будет окошко в настройках с названием оператора: теоретически можно разместить несколько плазмоидов с разными операторами на рабочем столе, да и для хранения данных (чтобы ничего не путалось) возможно это будет полезно.

    Если последнее слишком сложно, то лично меня устроит версия и с одним оператором.

    Пожалуйста, сделайте свой плазмоид универсальным и выложите!

    Люди отблагодарят. Как минимум добрым словом. А некоторые (я например) и материально.
  • 0
    размышляя я пришёл к тому, что в настройки нужно добавлять ещё и названия полей.

    Итого оптимальный вариант полей в настроек:
    — УРЛ (то что написано в action у формы)
    — регулярное выражение
    — название поля с логином в форме
    — логин
    — название поля с паролем
    — пароль
    — время обновления

    теоретически можно сократить на 3 поля, если все настройки логина и пароля вынести в одно поле так:
    login=vasilii&password=secretpupkin

    при последнем варианте мы можем дописать какие-либо ещё параметры, которые могут понадобиться для успешного логина.

    в принципе, чтобы защититься от злых провайдеров хорошо бы ещё поле с идентификатором браузера, а то если он будет фиксированный, то они просто его заблокируют вход этому плазмоиду раз и навсегда…

    Такой плазмоид мог бы помочь людям смотреть свой баланс в тысячах сервисов, а не только мобильных операторов: провайдеров, посредников и т.п…

    Последний вариант полей настроек:
    — УРЛ (то что написано в action у формы)
    — регулярное выражение
    — поле в котором все необходимые значения полей для логина типа login=vasilii&password=secretpupkin
    — регулярность обновления
    — идентификатор браузера

    Всего на 3 поля больше, чем в описанном вами плазмоиде, но это делает продукт ценным для практически всех линуксоидов!

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