Пользователь
0,0
рейтинг
13 июля 2009 в 13:19

Разработка → Пишем апплет для GNOME на Python

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

Апплет — это маленькое приложение, встраиваемое непосредственно в панель GNOME. Обычно апплет выполняет какой-то опеределенный узкий функционал (изменение громкости, монтирование устройств), что выгодно отличает его от громоздкого оконного приложения. На самом деле знающему Python кодеру не составит труда написать собственный апплет, но и для незнающих есть выход: большое количество полезных (и не очень) апплетов лежит на сайте gnomefiles.org, помимо этого, на официальном сайте есть примеры написания апплетов на C.

Поехали!


Итак, мы пишем апплет. Апплет должен что-то делать. Наш апплет, например, будет показывать хабракарму и хабрасилу. Для получения всей этой информации мы будем использовать API хабра.

#!/usr/bin/env python
# coding=utf-8
import sysos, gtk, gtk.gdk, pygtk, gnomeapplet, gnome
 
pygtk.require('2.0')
 
__USER__ = 'VladX'
__URL__ = 'http://habrahabr.ru/api/profile/' + __USER__


Вот так начинается наш апплет. Тут все понятно — импортируем нужные библиотеки, заносим в переменные нужные параметры. Во избежание всяких проблем с русскими символами будем использовать кодировку UTF-8 (и Вам, кстати, советую всегда поступать так же).

class MyApplet (gnomeapplet.Applet):
 
    def __init__ (self, applet, iid):
 
        self.applet = applet
        self.applet.set_name('MyApplet')
        self.hbox = gtk.HBox()
        self.applet.add(self.hbox)
        self.event = gtk.EventBox()
        self.hbox.add(self.event)
        self.info = gtk.Label()
        self.event.add(self.info) # Чтобы объект мог реагировать на различные события, его нужно поместить в Event Box
        self.event.set_tooltip_text('Хабраюзер ' + __USER__)
        self.event.connect('button-press-event'self.callback_button)
        self.__init_popupmenu()
        self.applet.connect('destroy'self.callback_destroy)
        self.applet.show_all() # Показываем все это на панели
        self.info.set_text(self.get_info())
 
    def __init_popupmenu (self):
 
        self.applet.setup_menu('''
            <popup name='
button3'>
                <menuitem name='
Open Item' verb='Open' stockid='gtk-open'/>
                <menuitem name='
About Item' verb='About' stockid='gtk-about'/>
            </popup>'
''[
                          ('Open'self.callback_open),
                          ('About'self.callback_about)
                         ]None)
 
    def get_info (self):
        '''Делаем запрос, например, при помощи Pycurl'''
        import pycurl, StringIO
        content = StringIO.StringIO()
        c = pycurl.Curl()
        c.setopt(pycurl.URL, __URL__)
        c.setopt(pycurl.FOLLOWLOCATION1)
        c.setopt(pycurl.WRITEFUNCTION, content.write) # Задаем метод, записывающий ответ в переменную
        c.perform()
        c.close()
        content = content.getvalue()
        '''Парсим код'''
        try:
            karma = content[content.index('<karma>')+7:content.index('</karma>')]
 
        except:
            karma = '*'
 
        try:
            rating = content[content.index('<rating>')+8:content.index('</rating>')]
 
        except:
            rating = '*'
 
 
        return karma + '/' + rating
 
    def callback_button (self, widget, event):
 
        self.callback_open(self)
 
    def callback_open (self, event, data=None):
        '''Открываем в браузере по клику'''
        gnome.url_show (
                        'http://' +
                        __USER__.replace('_''-') + # Заменяем в поддомене символы подчеркивания на дефисы
                        '.habrahabr.ru/'
                        )
 
    def callback_about (self, event, data=None):
        '''Показываем стандартное окошко среды GNOME'''
        os.system('gnome-about')
 
    def callback_destroy (self, applet):
        '''Уничтожаем объект'''
        del self.applet
 


Это, собственно, самая интересная часть апплета. Метод-конструктор __init__ () инициализирует объект, метод __init_popupmenu () добавляет в контекстное меню комманды «Открыть» и «О программе» и задает соответствующий им callback-метод. Метод get_info () получает ответ от сервера и обрабатывает его на предмет заветных чисел кармы и силы. Было бы глупо использовать специальный xml-парсер в столь тривиальной задаче, поэтому обойдемся простым быдлокодерским способом.

def applet_factory (applet, iid):
 
    MyApplet(applet, iid)
    return True
 
def main (args):
 
    if len(sys.argv) == 2 and sys.argv[1] == 'run-in-window':
 
        window = gtk.Window(gtk.WINDOW_TOPLEVEL)
        window.set_title('It works!')
        window.connect('destroy', gtk.main_quit)
        applet = gnomeapplet.Applet()
        applet_factory(applet, None)
        applet.reparent(window)
        window.show_all()
        gtk.main()
        sys.exit()
 
    elif len(sys.argv) == 2 and sys.argv[1] == 'help':
        '''Выводим хелп по параметрам'''
        print '''
        --run-in-window - run applet independent of gnome-panel
        --help - show this message'
''
 
    else:
 
        gnomeapplet.bonobo_factory('OAFIID:GNOME_MyApplet_Factory',
                                   MyApplet.__gtype__,
                                   'My Applet',
                                   '1.0',
                                   applet_factory)
 
 
 
if __name__ == '__main__':
    main(sys.argv)
 


Эти функции одинаковы в большинстве апплетов на Python, необходимо только изменить аргументы, передаваемые методу bonobo_factory на свои.
Также есть один полезный момент: при передаче скрипту параметра --run-in-window апплет запускается в отдельном окне, что очень помогает при отладке.

Ну вот, казалось бы, и все. Но нет — апплет написан, но в списке апплетов гнома его нет. Чтобы это исправить, нужно положить файлик (в данном случае можно использовать имя gnomeMyAppletFactory.server) в директорию /usr/lib64/bonobo/servers с примерно таким содержанием:

<oaf_info>
<oaf_server iid='OAFIID:GNOME_MyApplet_Factory' type='exe' location='/home/vlad/applets/src/habrapplet.py'>
    <oaf_attribute name='repo_ids' type='stringv'>
        <item value='IDL:Bonobo/GenericFactory:1.0' />
        <item value='IDL:Bonobo/Unknown:1.0' />
    </oaf_attribute>
    <oaf_attribute name='name' type='string' value='My Applet' />
    <oaf_attribute name='description' type='string' value='Show your karma and ratio' /> 
</oaf_server>
 
<oaf_server iid='OAFIID:GNOME_MyApplet' type='factory' location='OAFIID:GNOME_MyApplet_Factory'>
    <oaf_attribute name='repo_ids' type='stringv'>
        <item value='IDL:GNOME/Vertigo/PanelAppletShell:1.0' />
        <item value='IDL:Bonobo/Control:1.0' />
        <item value='IDL:Bonobo/Unknown:1.0' />
    </oaf_attribute>
    <oaf_attribute name='name' type='string' value='My First Applet' />
    <oaf_attribute name='name-ru' type='string' value='Хабрапплет' />
    <oaf_attribute name='description' type='string' value='Show your karma and ratio' />
    <oaf_attribute name='description-ru' type='string' value='Отображает вашу карму и хабрасилу' />
    <oaf_attribute name='panel:category' type='string' value='Utility' />
    <oaf_attribute name='panel:icon' type='string' value='computer.png' />
</oaf_server>
</oaf_info>


Не забываем поменять путь к скрипту на свой и разрешить его исполнение с помощью chmod +x.

Удачного коддинга, %username%!

p.s. При подготовке материала использовался ресурс highlight.hohli.com.
@czpukmen
карма
43,2
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

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

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

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

  • –1
    Где-то я это уже видел
    • +1
      Здесь вроде как рассматривается создание апплета. Многим, думаю, это будет интересно.
  • +2
    Спасибо за статью давно хотел написать какой то апплет для Гнома…
  • +2
    Спасибо за туториал, чего в жизни не пригодится.
    Кстати, плохо, что событие отрабатывает по нажатию любой кнопки. Для себя проблему решил таким образом:

    ---         self.callback_open(self)
    +++         if event.button == 1:
    +++             self.callback_open(self)
    

  • +2
    Это не очень хороший код на Python. Например: зачем столько «self.» в «init»? Вы дальше этими атрибутами где-то пользуетесь? gtk.gdk импортируется, но нигде не используется. Зачем?
  • +2
    Хоть бы скриншот прилепили, посмотреть, чего получилось.
  • +2
    Я бы только посоветовал вам брать хорошую привычку вместо:
      return karma + '/' + rating
    писать:
      return '%s/%s' % (karma, rating)
    И еще не мешало бы указать какой xml приходит по урлю. А так мне понравилось.
    • +1
      Понятно, что такой подход нужен для удобства перевода приложения, но не в таком-же случае)))
      Зачем переводчикам переводить слеш?
      • +1
        перевода на японский язык?? или что?.. Просто хорошая привычка
        • +1
          Да, перевода интерфейса приложения на другие языки. Просто по-хорошему все текстовые вставки, которые отображаются в UI выносятся в отдельные файлы и подставляются спец. функциями.
          Например _(«Hello, %s. Your rating is %s»)% (name, rating)
          Где _ это функция для выбора переведенного текста из файла перевода, где идентификатором является сам текст по умолчанию «Hello, %s. Your rating is %s».

          Переводчик (в смысле человек, который занимается локализацией) интерфейса приложения получает строку «Hello, %s. Your rating is %s» и переводит ее как «Привет, %s. Твой рейтинг %s»
          В итоге функция _(«Hello, %s. Your rating is %s») возвращает строку «Привет, %s. Твой рейтинг %s» в которой так-же без проблем проводит замену.

          Иначе пришлось бы писать _(«Hello, „)+name+_(“. Your rating is „)+rating Что неудобно как для кодера, так и для локализатора
          • 0
            я думаю, это программке до локализации, как до Китая. Но наверно вы в чем — то правы.
            • +2
              А вы по какой причине советовали писать так, а не иначе? Чисто карго культ?
  • –4
    пятоносила.
  • +1
    Я тоже когда-то подобную статейку писал — tilarids.blogspot.com/2008/06/gnome-python.html — может, кому пригодится. Думаю, что некоторая кривость кода выше объясняется тем, что автор копировал и компоновал код кусками. По себе знаю, я так с апплетом поступал :)
  • +1
    Как уже отметили выше, мой код не идеален, да… Наверное потому, что я больше Си'шник. От привычек никуда не денешся :) А gtk.gdk действительно нигде не используется, его можно не включать
  • +2
    Старая, но более подробная и понятная статья на эту тему есть у j2a. В трёх частях pyobject.ru/blog/tag/gnome/
  • –1
    Автор, очевидно, PEP 8 не читал (это я по поводу импортов).
  • 0
    Под какой лицензией можно использовать приведённый код?
    • +2
      Вы смеётесь?)
      • 0
        Нет, я уточняю ;)
        • 0
          Любая либеральная лицензия, какая Вам нравится ;)
          • 0
            Спасибо.
            Использовал фрагмент Вашего кода для похожей программки (не апплет, а просто «плавающее окошко» типа zoclock): висит поверх всего, чего можно, подходит для любых WM/DE (возможно, даже для Windows, если для него есть нужные питоньи модули).
  • 0
    Это:
    import pycurl, StringIO
    content = StringIO.StringIO()
    c = pycurl.Curl()
    c.setopt(pycurl.URL, __URL__)
    c.setopt(pycurl.FOLLOWLOCATION, 1)
    c.setopt(pycurl.WRITEFUNCTION, content.write) # Задаем метод, записывающий ответ в переменную
    c.perform()
    c.close()
    content = content.getvalue()

    Можно сделать так:
    import urllib2
    content = urllib2.urlopen(__URL__).read()
  • 0
    Понимаю, что тема старая, но никак не могу найти инфу, перерыл весь гугл.
    Подскажите пожалуйста, как обновить, скажем, по клику (а если реалтайм, то вообще идеально), информацию (текст) на апплете?

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