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

    Полный код плазмоида вы можете найти здесь.
    Метки:
    Поделиться публикацией
    Похожие публикации
    Реклама помогает поддерживать и развивать наши сервисы

    Подробнее
    Реклама
    Комментарии 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
                  Огромная просьба: добавьте в ваш плазмоид ещё 2 поля настройки: УРЛ и регулярное выражение. Выложите его куда-нибудь.

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

                  Спасибо!

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

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

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

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

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

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

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

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

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

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

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

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

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