Criando Scripts do Desktop do Linux, Parte 1: Básico

Faça com que o Python e screenlets participem do trabalho na criação de aplicativos de desktop úteis

Esta série de artigos explora como usar Python para criar scripts para o desktop GNOME, a estrutura de screenlets e Nautilus para fornecer um ambiente altamente produtivo. Scripts no desktop ativam a funcionalidade de arrastar e soltar e o acesso rápido às informações e aos serviços comumente usados. Nesta parte, saiba como criar um aplicativo de desktop usando o kit de ferramentas de widgets de screenlets.

Paul Ferrill, CTO, ATAC

Paul Ferrill escreve nos meios de comunicação de comércio de computadores há mais de 20 anos. Começou escrevendo revisões de rede para o PC Magazine sobre produtos como LANtastic e versões anteriores do Novell Netware. Paul é graduado e possui mestrado em engenharia elétrica e escreveu softwares para mais arquiteturas e plataformas de computador do que consegue se lembrar.



18/Abr/2010

O desenvolvimento de aplicativos para o desktop do Linux requer tipicamente algum tipo de estrutura de interface gráfica com o usuário (GUI) sobre a qual desenvolver. As opções incluem GTK+ para o desktop do GNOME e Qt para o K Desktop Environment (KDE). As duas plataformas oferecem tudo que um desenvolvedor precisa para criar um aplicativo GUI, incluindo bibliotecas e ferramentas de layout para criar as janelas que são vistas pelos usuários. Este artigo mostra como criar aplicativos de produtividade de desktop baseados no kit de ferramentas de widgets de screenlets (consulte Recursos para obter um link).

Diversos aplicativos existentes se ajustariam na categoria de produtividade de desktop, incluindo GNOME Do e Tomboy. Esses aplicativos tipicamente permitem que os usuários interajam com eles diretamente a partir do desktop por meio de uma combinação de teclas especial ou arrastando e soltando de outro aplicativo como Mozilla Firefox. Tomboy funciona como uma ferramenta de anotação de desktop que oferece suporte a soltar texto de outras janelas.

Obtendo uma introdução a screenlets

Poucas coisas precisam ser instaladas para iniciar o desenvolvimento de screenlets. Primeiro, instale o pacote de screenlets usando o Ubuntu Software Center ou a linha de comando. No Ubuntu Software Center, digite screenlets na caixa Search . Duas opções deverão ser exibidas para o pacote principal e uma instalação separada para a documentação.

Python e Ubuntu

Os screenlets são programados com o Python. A instalação básica do Ubuntu 10.04 tem Python versão 2.6 instalado, pois muitos utilitários dependem dele. Poderão ser necessárias bibliotecas adicionais dependendo dos requisitos do aplicativo. Para o propósito deste artigo, instalei e testei tudo no Ubuntu versão 10.04.

Em seguida, transfira por download a origem do screenlet de teste do site screenlets.org. O screenlet de teste reside na pasta src/share/screenlets/Test e usa Cairo e GTK, que também precisam ser instalados. O código de origem inteiro do programa de teste está no arquivo TestScreenlet.py. Abra esse arquivo no seu editor favorito para ver a estrutura básica de um screenlet.

Python é altamente orientado a objetos e, desse modo, usa a palavra-chave class para definir um objeto. Neste exemplo, a classe é denominada TestScreenlet e tem uma série de métodos definidos. No TestScreenlet.py, observe o seguinte código na linha 42:

def __init__(self, **keyword_args):

Python usa a notação de sublinhado duplo à esquerda e à direita (__) para identificar as funções do sistema com comportamentos predefinidos. Nesse caso, a função __init__ é para todos os intentos e propósitos o construtor da classe e contém qualquer número de etapas de inicialização a serem executadas na criação de uma nova instância do objeto. Por convenção, o primeiro argumento de todo método de classes é uma referência à instância atual da classe e é denominado self. Esse comportamento facilita usar self para referenciar métodos e propriedades da instância em que estiver:

self.theme_name = "default"

A estrutura dos screenlets define várias normas de convenções de nomenclatura, como descrito na página do desenvolvedor do screenlets.org (consulte Recursos para obter um link). Há um link para o código de origem do pacote de screenlets junto com a documentação da interface de programação de aplicativos (API). Olhar no código também fornece um insight do que cada função faz com os argumentos de chamada e o que ele retorna.


Escrevendo um screenlet simples

Os componentes básicos de um screenlet incluem um arquivo de ícones, o arquivo de código de origem e uma pasta de temas. A pasta de temas contém pastas adicionais de temas diferentes. Um modelo de amostra pode ser encontrado em screenlets.org com os arquivos e pastas requeridos para ajudá-lo a obter uma introdução.

Para este primeiro exemplo, use um modelo fornecido para criar um aplicativo "Hello World" básico. O código desse aplicativo básico é mostrado na Listagem 1.

Listagem 1. Código do Python para o screenlet Hello World
#!/usr/bin/env python

import screenlets

class HelloWorldScreenlet(screenlets.Screenlet):
    __name__ = 'HelloWorld'
    __version__ = '0.1'
    __author__ = 'John Doe'
    __desc__ = 'Simple Hello World Screenlet'

    def __init__(self, **kwargs):
        # Customize the width and height.
        screenlets.Screenlet.__init__(self, width=180, height=50, **kwargs)

    def on_draw(self, ctx):
        # Change the color to white and fill the screenlet.
        ctx.set_source_rgb(255, 255, 255)
        self.draw_rectangle(ctx, 0, 0, self.width, self.height)

        # Change the color to black and write the message.
        ctx.set_source_rgb(0, 0, 0)
        text = 'Hello World!'
        self.draw_text(ctx, text, 10, 10, "Sans 9" , 20, self.width)


if __name__ == "__main__":
    import screenlets.session
    screenlets.session.create_session(HelloWorldScreenlet)

Cada aplicativo deve importar a estrutura de screenlets e criar uma nova sessão. Existem alguns outros requisitos mínimos, incluindo etapas de inicialização junto com uma função de desenhar básica para apresentar o widget na tela. O exemplo de TestScreenlet.py tem um método __init__ que inicializa o objeto. Neste caso, pode ser vista uma linha única com uma chamada para o método __init__ do screenlet, que configura o peso e a largura inicial da janela a ser criada para esse aplicativo.

A outra única função necessária para esse aplicativo é o método on_draw . Essa rotina configura a cor do plano de fundo da caixa para branco e desenha um retângulo com as dimensões definidas anteriormente. Ela configura a cor do texto para preto e o texto de origem para "Hello World!" e desenha o texto. A Figura 1 mostra o que deverá ser visto ao executar esse screenlet. Essa estrutura básica permanece com você durante o restante deste artigo, enquanto você desenvolve sobre esses blocos simples para criar aplicativos mais úteis.

Figura 1. Estrutura básica do screenlet
Image showing the basic structure of a screenlet

Reutilizando o código em um screenlet mais complexo

Um detalhe interessante sobre a composição de screenlets é o recurso de reutilizar código de outros aplicativos. A reutilização de código abre um mundo de possibilidades com a faixa ampla de projetos de software livre baseados na linguagem Python. Cada screenlet tem a mesma estrutura básica, mas com mais métodos definidos para tratar os diferentes comportamentos. A Listagem 2 mostra um aplicativo de amostra denominado TimeTrackerScreenlet.

Listagem 2. Código Python para o screenlet Time Tracker
#!/usr/bin/env python

import screenlets
import cairo
import datetime

class TimeTrackerScreenlet(screenlets.Screenlet):
	__name__ = 'TimeTrackerScreenlet'
	__version__ = '0.1'
	__author__ = 'John Doe'
	__desc__ = 'A basic time tracker screenlet.'
	
	theme_dir = 'themes/default'
	image = 'start.png'

	def __init__(self, **keyword_args):
		screenlets.Screenlet.__init__(self, width=250, height=50, **keyword_args)
		self.add_default_menuitems()
		self.y = 25
		self.theme_name = 'default'
		self.on = False
		self.started = None

	def on_draw(self, ctx):
		self.draw_scaled_image(ctx, 0, 0, self.theme_dir + '/' +
		self.image, self.width, self.height)
		
	def on_mouse_down(self, event):
		if self.on:
			self.started = datetime.datetime.now()
			self.image = 'stop.png'
			self.on = False
		else:
			if self.started:
				length = datetime.datetime.now() - self.started
				screenlets.show_message(None, '%s seconds' %
				length.seconds, 'Time')
				self.started = None
			self.image = 'start.png'
			self.on = True

	def on_draw_shape(self, ctx):
		self.on_draw(ctx)
		ctx.rectangle(0, 0, self.width, self.height)
		ctx.fill()
	

if __name__ == "__main__":
	import screenlets.session
	screenlets.session.create_session(TimeTrackerScreenlet)

Esse exemplo apresenta mais alguns conceitos que precisam ser entendidos antes de começar o desenvolvimento de alguma coisa útil. Todos os aplicativos screenlet têm a capacidade de responder a ações ou eventos específicos do usuário, como cliques do mouse ou operações de arrastar e soltar. Nesse exemplo, o evento de mouse para baixo é usado como um acionador para alterar o estado do ícone. Quando o screenlet é executado, a imagem start.png é exibida. Clicar na imagem faz com que ela mude para stop.png e registra o tempo iniciado no self.started. Clicar na imagem de parar altera a imagem de volta para start.png e exibe o tempo decorrido desde que a primeira imagem de iniciar foi clicada.

Responder a eventos é outro recurso principal que torna possível criar qualquer número de aplicativos diferentes. Embora este exemplo use somente o evento mouse_down é possível usar a mesma abordagem para outros eventos gerados pela estrutura de screenlets ou por um evento de sistema como um cronômetro. O segundo conceito apresentado aqui é o de estado persistente. Como o aplicativo está em execução continuamente, aguardando um evento para acionar alguma ação, ele é capaz de controlar itens na memória, como o horário em que a imagem de início foi clicada. Também é possível salvar informações no disco para recuperação posterior, se necessário.


Automatizando tarefas com screenlets

Agora que você tem a ideia geral sobre o desenvolvimento de screenlets, vamos colocar tudo isso junto. A maioria dos usuários atualmente usa um leitor Really Simple Syndication (RSS) para ler blogs e alimentações de notícias. Para este último exemplo, vamos criar um screenlet configurável que monitora alimentações específicas de palavras-chave e exibe quaisquer ocorrências em uma caixa de texto. Os resultados serão links clicáveis para abrir a postagem no navegador da Web padrão. A Listagem 3 mostra o código de origem do screenlet RSS Search.

Listagem 3. Código Python para o screenlet RSS Search
#!/usr/bin/env python

from screenlets.options import StringOption, IntOption, ListOption
import xml.dom.minidom
import webbrowser
import screenlets
import urllib2
import gobject
import pango
import cairo

class RSSSearchScreenlet(screenlets.Screenlet):
    __name__ = 'RSSSearch'
    __version__ = '0.1'
    __author__ = 'John Doe'
    __desc__ = 'An RSS search screenlet.'

    topic = 'Windows Phone 7'
    feeds = ['http://www.engadget.com/rss.xml',
             'http://feeds.gawker.com/gizmodo/full']
    interval = 10

    __items = []
    __mousesel = 0
    __selected = None

    def __init__(self, **kwargs):
        # Customize the width and height.
        screenlets.Screenlet.__init__(self, width=250, height=300, **kwargs)
        self.y = 25

    def on_init(self):
        # Add options.
        self.add_options_group('Search Options',
                               'RSS feeds to search and topic to search for.')
        self.add_option(StringOption('Search Options',
            'topic',
            self.topic,
            'Topic',
            'Topic to search feeds for.'))
        self.add_option(ListOption('Search Options',
                                   'feeds',
                                   self.feeds,
                                   'RSS Feeds',
                                   'A list of feeds to search for a topic.'))
        self.add_option(IntOption('Search Options',
                                  'interval',
                                  self.interval,
                                  'Update Interval',
                                  'How frequently to update (in seconds)'))

        self.update()

    def update(self):
        """Search selected feeds and update results."""

        self.__items = []

        # Go through each feed.
        for feed_url in self.feeds:

            # Load the raw feed and find all item elements.
            raw = urllib2.urlopen(feed_url).read()
            dom = xml.dom.minidom.parseString(raw)
            items = dom.getElementsByTagName('item')

            for item in items:

                # Find the title and make sure it matches the topic.
                title = item.getElementsByTagName('title')[0].firstChild.data
                if self.topic.lower() not in title.lower(): continue

                # Shorten the title to 30 characters.
                if len(title) > 30: title = title[:27]+'...'

                # Find the link and save the item.
                link = item.getElementsByTagName('link')[0].firstChild.data
                self.__items.append((title, link))

        self.redraw_canvas()

        # Set to update again after self.interval.
        self.__timeout = gobject.timeout_add(self.interval * 1000, self.update)

    def on_draw(self, ctx):
        """Called every time the screenlet is drawn to the screen."""

        # Draw the background (a gradient).
        gradient = cairo.LinearGradient(0, self.height * 2, 0, 0)
        gradient.add_color_stop_rgba(1, 1, 1, 1, 1)
        gradient.add_color_stop_rgba(0.7, 1, 1, 1, 0.75)
        ctx.set_source(gradient)
        self.draw_rectangle_advanced (ctx, 0, 0, self.width - 20,
                                      self.height - 20,
                                      rounded_angles=(5, 5, 5, 5),
                                      fill=True, border_size=1,
                                      border_color=(0, 0, 0, 0.25),
                                      shadow_size=10,
                                      shadow_color=(0, 0, 0, 0.25))

        # Make sure we have a pango layout initialized and updated.
        if self.p_layout == None :
            self.p_layout = ctx.create_layout()
        else:
            ctx.update_layout(self.p_layout)

        # Configure fonts.
        p_fdesc = pango.FontDescription()
        p_fdesc.set_family("Free Sans")
        p_fdesc.set_size(10 * pango.SCALE)
        self.p_layout.set_font_description(p_fdesc)

        # Display our text.
        pos = [20, 20]
        ctx.set_source_rgb(0, 0, 0)
        x = 0
        self.__selected = None
        for item in self.__items:
            ctx.save()
            ctx.translate(*pos)

            # Find if the current item is under the mouse.
            if self.__mousesel == x and self.mouse_is_over:
                ctx.set_source_rgb(0, 0, 0.5)
                self.__selected = item[1]
            else:
                ctx.set_source_rgb(0, 0, 0)

            self.p_layout.set_markup('%s' % item[0])
            ctx.show_layout(self.p_layout)
            pos[1] += 20
            ctx.restore()
            x += 1

    def on_draw_shape(self, ctx):
        ctx.rectangle(0, 0, self.width, self.height)
        ctx.fill()

    def on_mouse_move(self, event):
        """Called whenever the mouse moves over the screenlet."""

        x = event.x / self.scale
        y = event.y / self.scale
        self.__mousesel = int((y -10 )/ (20)) -1
        self.redraw_canvas()

    def on_mouse_down(self, event):
        """Called when the mouse is clicked."""

        if self.__selected and self.mouse_is_over:
            webbrowser.open_new(self.__selected)


if __name__ == "__main__":
    import screenlets.session
    screenlets.session.create_session(RSSSearchScreenlet)

Criando com base nos conceitos dos dois primeiros exemplos, este screenlet usa uma série de novos conceitos, incluindo a página de configuração. Na rotina on_init , três opções são incluídas para o usuário especificar: uma lista de alimentações RSS para controlar, um tópico de interesse para pesquisar e um intervalo de atualização. A rotina de atualização utiliza todos eles ao executar.

Python é uma linguagem excelente para esse tipo de tarefa. A biblioteca padrão inclui tudo que é necessário para carregar a Linguagem de Marcação Extensível (XML) de uma alimentação RSS para uma lista pesquisável. No Python, isso toma apenas três linhas de código:

raw = urllib2.urlopen(feed_url).read()
dom = xml.dom.minidom.parseString(raw)
items = dom.getElementsByTagName('item')

As bibliotecas usadas nessas três linhas incluem urllib2 e xml. Na primeira linha, o conteúdo inteiro localizado no endereço feed_url é lido na cadeia de caractere bruta. Em seguida, como se sabe que essa cadeia de caractere contém XML, usa-se o método dom.minidom.parseString da biblioteca Python XML para criar um objeto do documento composto de objetos de nós.

Finalmente, cria-se uma lista de objetos de elemento correspondente aos elementos XML individuais denominada . Em seguida, é possível iterar nessa lista para procurar o tópico de destino. Python tem uma maneira bastante elegante de iterar em uma lista de itens usando a palavra-chave for , como neste fragmento de código:

for item in items:
    # Find the title and make sure it matches the topic.
    title = item.getElementsByTagName('title')[0].firstChild.data
    if self.topic.lower() not in title.lower(): continue

Cada item que corresponder ao critério é incluído na lista exibida atualmente, que está associada a essa instância do screenlet. Usar essa abordagem torna possível ter múltiplas instâncias do mesmo screenlet em execução, cada uma configurada para procurar tópicos diferentes. A parte final da função de atualização desenha novamente o texto com a lista atualizada e dispara um novo cronômetro de atualização baseado no intervalo da página de configuração. Por padrão, o cronômetro dispara a cada 10 segundos, embora isso possa ser mudado para o valor que se desejar. O mecanismo do cronômetro vem da biblioteca gobject , que é parte da estrutura GTK.

Esse aplicativo expande o método on_draw de maneira bem pesada para acomodar a nova funcionalidade. As duas bibliotecas, Cairo e Pango, tornam possível criar alguns dos efeitos usados na janela de texto. Usar um matiz dá ao plano de fundo do widget uma bela aparência, junto com ângulos arredondados e semitransparência. Usar Pango para layout adiciona uma série de funções para salvar e restaurar o contexto atual com facilidade. Também fornece uma maneira de gerar fontes escaláveis com base no tamanho atual do screenlet.

A parte mais trabalhosa do método on_draw é manipular quando um usuário passa o mouse sobre um item da lista. Usando a palavra-chave for" é possível iterar os itens do screenlet para ver se o usuário está passando o mouse sobre esse item específico. Em caso positivo, você configura a propriedade selecionada e altera a cor para fornecer feedback visual. Também usa um bit de marcação para configurar a propriedade de link para negrito—provavelmente não é a maneira mais elegante ou eficiente de tratar o problema, mas funciona. Quando um usuário clicar em um dos links na caixa, um navegador da Web é ativado com a URL de destino. Essa funcionalidade pode ser observada na função on_mouse_down . Python e suas bibliotecas tornam possível ativar o navegador da Web padrão para exibir a página desejada com uma única linha de código. A Figura 2 mostra um exemplo desse screenlet.

Figura 2. O screenlet de exemplo
Image showing the example screenlet

Resumo

Criar aplicativos de desktop úteis não é uma tarefa difícil com Python e screenlets. O maior obstáculo é ficar à vontade com a API de screenlets e a mecânica de passar o controle entre funções diferentes. Embora a documentação possa não ser uma leitura fácil, ela contém as informações necessárias para usar as diferentes funções. Uma maneira ainda melhor de fazer alguma coisa funcionar com rapidez é modificar um screenlet existente que esteja perto do que se deseja.

Recursos

Aprender

Obter produtos e tecnologias

  • Avalie produtos IBM da maneira que for melhor para você: faça download da versão de teste de um produto, avalie um produto on-line, use-o em um ambiente de nuvem ou passe algumas horas na SOA Sandbox aprendendo a implementar a Arquitetura Orientada a Serviços de modo eficiente.

Discutir

  • Participe da comunidade My developerWorks. Entre em contato com outros usuários do developerWorks e explore os blogs, fóruns, grupos e wikis voltados para desenvolvedores.

Comentários

developerWorks: Conecte-se

Los campos obligatorios están marcados con un asterisco (*).


Precisa de um ID IBM?
Esqueceu seu ID IBM?


Esqueceu sua senha?
Alterar sua senha

Ao clicar em Enviar, você concorda com os termos e condições do developerWorks.

 


A primeira vez que você entrar no developerWorks, um perfil é criado para você. Informações no seu perfil (seu nome, país / região, e nome da empresa) é apresentado ao público e vai acompanhar qualquer conteúdo que você postar, a menos que você opte por esconder o nome da empresa. Você pode atualizar sua conta IBM a qualquer momento.

Todas as informações enviadas são seguras.

Elija su nombre para mostrar



Ao se conectar ao developerWorks pela primeira vez, é criado um perfil para você e é necessário selecionar um nome de exibição. O nome de exibição acompanhará o conteúdo que você postar no developerWorks.

Escolha um nome de exibição de 3 - 31 caracteres. Seu nome de exibição deve ser exclusivo na comunidade do developerWorks e não deve ser o seu endereço de email por motivo de privacidade.

Los campos obligatorios están marcados con un asterisco (*).

(Escolha um nome de exibição de 3 - 31 caracteres.)

Ao clicar em Enviar, você concorda com os termos e condições do developerWorks.

 


Todas as informações enviadas são seguras.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=80
Zone=Linux
ArticleID=648406
ArticleTitle=Criando Scripts do Desktop do Linux, Parte 1: Básico
publish-date=04182010