Programando plasmoids com Javascript

O Plasma tem um interpretador JavaScript embutido (também c0nhecido como ECMAScript, e frequentemente referenciado como um QtScript no contexto do Qt) que permite escrever plasmoids sem depender de componentes externos.

Introdução

Nesse tutorial cobriremos a criação de plasmoids simples com Javascript, também  conhecido como QtScript ou ECMAScript, que fornecem widgets para o Plasma.
 
Usar o JavaScript não requer nenhuma dependência externa, já que a biblioteca Qt possui seu próprio interpretador da linguagem.  No KDe 4.3, o engine de JavaScript do Plasma é parte de kdebase-runtime, e assim pode ser usado em qualquer aplicação baseada no Plasma, não somente no Plasma Desktop.

QtScript

A API Javascript simplificada é fornecida pelo sistema QtScript do Qt que provê acesso a todos os recursos do interpretador ECMA. Se funciona com o ECMAScript, irá funcionar com o Javascript simplificado do Plasmoid.
No topo da linguagem de script ECMA, o QtScript provê integração com o recursos do Qt. Provavelmente o uso mais útil no contexto do desenvolvimento de plasmoids é o uso de signals e slots que são o mecanismo de chamadas do Qt. Signals podem ser emitidos com o QtScript chamando o metódo correspondente, um signal pode ser conectado a um slot usando o metódo connect() (e disconecte com o disconnect() ) e qualquer função definida no Plasmoid pode ser usado como um slot. Por exemplo:

function onClick()
{
    print("We got clicked!")
}
 
function onFirstClick()
{
    print("First click!")
    button.clicked.disconnect(onFirstClick)
}
 
button = new PushButton
button.clicked.connect(onClick)
button.clicked.connect(onFirstClick)
button.clicked()

Isso irá imprimir:

We got clicked!
First click!

no console quando o plasmoid for iniciado, e “We got clicked!” novamente toda vez que o botão for clicado pelo usuário.

O que é um widget?

No contexto do plasma, um widget é um objeto gráfico único e contido no canvas. Eles podem ser adicionados e removidos individualmente, configurados separadamente de outros widgets, etc. Esses mini aplicativos são muitas vezes referenciados em outras plataformas como “applets”, “apps”, “gadgets”, “karambas”, “desklets”. Nós escolhemos a expressão widget para o Plasma por  ser um termo usado em outros sistemas existentes.
A API de um widget é definida pelo hospedeiro “ScriptEngine”, com a exceção dos widgets nativos do Plasma escritos em C++ onde é permitido que as bibliotecas que usam a API do Plasma sejam acessadas diretamente. Atualmente o Plasma suporta tanto widgets nativos em C++ quanto escritos em libguagens dinâmicas como:

  • SuperKaramba’s karambas
  • Enlightenment 17 edje content
  • Google Gadgets
  • MacOS dashboard widgets (não todos)

O que é um Plasmoid?

Um plasmoid é um widget que pode ser carregado no Plasma, usa a API nativa do Plasma e vem empacotado em um único arquivo que inclue código, metadados, imagens, configurações, etc. Plasmoids podem sem escritos usando diversas linguagens de script e APIs. Esse tutorial foca na API JavaScript Simplificada.

Estrutura do projeto

O primeiro passo para qualquer projeto é montar a estrutura de diretório no disco. Para esse tutorial, iremos criar um simples plasmoid “hello-javascript”. Crie um diretório em algum lugar chamado hello-javascript, que contém um diretório content onde fica o diretório code. O comando abaixo cria todos esses diretórios:

mkdir -p hello-javascript/contents/code

A única parte necessária da estrutura, de fato, é o diretório contents – quase tudo estará contido nesse diretório. Mas separar o código dos dados da aplicação é um bom hábito a ter.

Metadata.desktop

No diretório hello-javascript crie um arquivo chamado metadata.desktop e abra em seu editor de texto. Copie o código abaixo no arquivo:

[Desktop Entry]
Name=Hello JavaScript
Comment=An example JavaScript widget
Icon=chronometer
 
Type=Service
X-KDE-ServiceTypes=Plasma/Applet
 
X-Plasma-API=javascript
X-Plasma-MainScript=code/main.js
X-Plasma-DefaultSize=200,100
 
X-KDE-PluginInfo-Author=<Your name here>
X-KDE-PluginInfo-Email=<Your email here>
X-KDE-PluginInfo-Name=hello-javascript
X-KDE-PluginInfo-Version=1.0
X-KDE-PluginInfo-Website=http://plasma.kde.org/
X-KDE-PluginInfo-Category=Examples
X-KDE-PluginInfo-Depends=
X-KDE-PluginInfo-License=GPL
X-KDE-PluginInfo-EnabledByDefault=true

Esse arquivo lista informações importantes que são necessárias para que o Plasma consiga carregar o widget, e também informações sobre o que o widget é e que o criou.
O campo Name fornece um nome para o widget quando o usuário abre a caixa de dialógo “Add Widget”.  Podem existir diferentes entradas para cada idioma. Por exemplo, você pode adicionar uma tradução para o holandes inserindo a linha Name[nl]=Hallo JavaScript.
O campo Comments fornece uma descrição mais detalhada do widget. Esta descrição também é mostrada na caixa de dialógo “Add Widget”, e pode ser tranduzida da mesma forma que o campo Name.
O campo Icon fornece o nome do icone associado com esse plasmoid. O icone é mostrado na caixa de dialógo “Add Widget”. Precisa ser nome de um icone que é distribuido com o KDE ou um fornecido por seu plasmoid. Se quiser usar um icone fornecido pelo seu plasmoid, use Icon=icon.png.
Os campos Type, X-KDE-ServiceType, X-Plasma_API e X-Plasma-MainScript são necessários para que o Plasma encontre o seu plasmoid e saiba o que fazer com ele. Note que o X-Plasma-MainScript é o caminho relativo para o diretório contents.
O X-Plasma-DefaultSize especifica o tamanho padrão de seu widget, com width,height. Apesar da unidade não ser tecnicamente pixel, elas tem o mesmo valor de um pixel que garantem que você não faça nada como aumentar ou diminuir o zoom em seu desktop.
P X-KDE-PluginInfo-Categories fornece uma categoria para o widget. Esse valor é usado pela caixa de dialógo “Add Widget”  para agrupar os widgets. O valor precisa ser uma das categorias listadas em Projects/Plasma/PIG.
O X-KDE-PluginInfo-Name é o nome interno do plasmoid (você pode pensar nelo como o nome do plasmoid, ao contrário do nome do widget que é dado por Name). Esse é o nome que você usará como argumento para o plasmoidviewer ou plasmapkg quando precisar fornecer um nome de plasmoid ao invés de um caminho. Não tem que ser o mesmo nome do diretório onde está contido o arquivo metadata.desktop.
Os campos X-KDE-PluginInfo-Author, X-KDE-PluginInfo-Email, X-KDE-PluginInfo-Version, X-KDE-PluginInfo-Website and X-KDE-PluginInfo-License são informações, e serão mostrados na caixa de dialogo About do plasmoid que pode ser exibida clicando no icone de informação próximo ao widget correspondente na caixa de dialogo “Add widgets” do plasma.
O campo X-KDE-PluginInfo-EnabledByDefault e X-KDE-PluginInfo-Depends raramente precisam ser alterados dos valores dados aqui.

Script principal

O nome do arquivo do script principal é dados por X-Plasma-MainScript no arquivo metadata.desktop, e é relativo ao diretório contents. Em nosso caso, nós precisamos criar o arquivo hello-javascript/contents/code/main.js. Coloque o código abaixo nesse arquivo:

layout = new LinearLayout(plasmoid);
 
label = new Label();
layout.addItem(label);
 
label.text = 'Hello JavaScript!';

Para começar, criamos um layout, já que os plasmoids não tem um layout padrão. Isso apenas garante que o label  tem o tamanho certo e está no lugar correto. Passando plasmoid para LinearLayout anexa o layout ao widget.
O próximo passo adiciona um label ao layout, e finalmente nós configuramos o texto do label.

Testando (KDE 4.3+)

Se você tem o KDe 4.3 ou mais recente, pode testar seu plasmoid sem intala-lo entrando no diretório hello-javascript e executando o plasmoidviewer. Alternativamente, chame

plasmoidviewer /path/to/hello-javascript

Isso é bastante conveniente para o desenvolvimento já que permite que você edite o plasmoid e teste as mudanças imediatamente sem ter que re-instalar o plasmoid várias vezes.

Instalando

Você pode instalar o plasmoid executando o comando abaixo de dentro do diretório hello-javascript:

plasmapkg -i .

O último argumento fornecido ao plasmapkg é o caminho do diretório que contém o arquivo metadata.desktop e o diretório contents.
Você pode agora adicionar seu widget ao desktop usando a caixa de dialogo “Add Widget”, ou visualiza-lo através do comando

plasmoidviewer hello-javascript

Empacotando

Se você quiser compartilhar seu plasmoid, terá que empacota-lo. Execute os seguinte comandos de dentro do diretório hello-javascript:

zip -r ../hello-javascript.zip . &&
mv ../hello-javascript.zip ../hello-javascript.plasmoid

Você tem agora um pacote contendo um plasmoid que você pode compartilhar com o mundo. Para instala-lo, use a funcionalidade “Install new widgets” da caixa de dialogo “Add widget”, ou, no diretório onde está localizado o arquivo hello-plasmoid.plasmoid execute:

plasmapkg -i hello-javascript.plasmoid

Para desinstalar o plasmoid, use o comando plasmapkg novamente com a opção -r:

plasmapkg -r hello-javascript

Obtendo dados

O Plasma possui um mecanismo de abstração de dados chamado DataEngine. Você pode chamar o DataEngine por nome, requisitar uma fonte dele (novamente, pelo nome) e obter dados de volta, seja em intervalos regulares ou assim que for atualizado pelo DataEngine internamente. O dado é um mapa (que é o mesmo que um objeto em JavaScript) de chaves string para dados.
No exemplo abaixo, nós pegaremos a hora local do mecanismo de hora.

Script principal

Nosso arquivo contents/code/main.js terá o seguinte conteudo:

layout = new LinearLayout(plasmoid);
 
label = new Label();
layout.addItem(label);
 
plasmoid.dataUpdated = function(name, data) {
	label.text = data.Time;
}
 
dataEngine("time").connectSource("Local", plasmoid, 500);

Existem duas coisas novas nesse plasmoid. Nós tivemos que implmentar o plasmoid.dataUpdated e conecta-lo à fonte do macanismo de dados.
Se você conectar uma fonte de dados a um object, quando houver uma atualização para essa fonte o metódo object.dataUpdated é chamado com dois argumentos: o nome da fonte e um mapa contendo os dados fornecidos pela fonte.
Nesse caso, o plasmoid.dataUpdated é chamado com “Local” no primeiro argumento e o objeto seguinte como segundo argumento:

{
    Timezone_Continent: 'Europe',
    Offset: 3600,
    Timezone: 'Europe/London',
    Time: new Date('Sun May 17 20:06:20 2009 GMT+0100'),
    Timezone_City: 'Guernsey'
}

A última linha do script concretiza a conexão atual. Nós selecionamos o mecanismo de dados “time” e requisitamos a fonte “Local” dele, que fornece a hora local. Nós dizemos para ele se conectar ao objeto plasmoid e forçamos uma atualização a cada 500ms (a cada meio segundo).
Se  nós deixássemos o último parâmetro como connectSource (isto é, não fornecendo um intervalo de atualização), então a fonte de dados deveser atualizada quando existir novo data disponivel. Isso não faz sentido para um relógio, onde o tempo está continuamente mudando, então não conseguiremos nenhum update se isso for feito. Todavia, existem outros mecanismos de dados (como o mecanismo “soliddevice”) onde você apenas quer ser notificado quando alguma coisa mudar, e nesse caso você não deve usar um intervalo de atualização.

Conectando Engines a Interface do Plama

Vários elementos de interface do Plasma suportam a conexão de DataEngine diretamente a eles. Eles incluem:

  • Label
  • Meter
  • TextEdit

Com esses widgets, alguem pode simplesmente direcionar o DataEngine para atualizar o widget diretamente e exibirá os dados. Então, em nosso exemplo, nós poderiamos fazer algo assim:
 

layout = new LinearLayout(plasmoid);
 
label = new Label();
layout.addItem(label);
 
dataEngine("time").connectSource("Local", label, 500);

Mesmo que isso não seja tão flexivel quanto implementar o plasmoid.dataUpdated, pode ser uma técnica útil.

Dica: plasmaengineexplorer

Para saber quais mecanismos estão disponíveis e que recursos eles fornecem, execute

plasmaengineexplorer

Você pode escolher qualquer um dos mecanismos instalados e requisitar uma fonte de dados, incluindo a configuração de um intervalo de atualização. Lembre-se somente de que quando você ler dados em JavaScript, os espaços nas chaves fornecidas pelas fontes de dados são trocadas por sublinhados. Assim cada fonte de dados do mecanismo “time”  fornece uma chave “Timezone Continent”, mas em JavaScript elas devem ser acessadas como
data.Timezone_Continent or data["Timezone_Continent"].

Now Playing

Aqui mostraremos algumas técnicas mais avançadas para lidar com mecanismos de dados, além de lhe fornecer no fim um plasmoid útil para exibir informações personalizadas sobre o que seu tocados de mídia favorito estiver tocando (assumindo, naturalmente, que seu tocador de mídia favorito seja suportado pelo mecanismo de dado “Now Playing”).

Script principal

Crie um arquivo contents/code/main.js com o seguinte conteúdo:

layout = new LinearLayout(plasmoid);
layout.orientation = QtVertical;
 
label = new Label();
layout.addItem(label);
label.text = "No player found";
 
function firstSource() {
	var sources = dataEngine("nowplaying").sources;
	if (sources.length) {
		return sources[0];
	} else {
		label.text = "No player found";
		return '';
	}
}
 
plasmoid.dataUpdated = function(name, data) {
	if (source == name) {
		label.text = 'Info:\n';
		for (var key in data) {
			label.text += key + ': ' + data[key] + '\n';
		}
	}
}
 
source = firstSource();
 
npDataEngine = dataEngine("nowplaying");
 
npDataEngine.sourceRemoved.connect(function(name) {
	if (name == source) {
		source = firstSource();
		if (source) {
			npDataEngine.connect(source, plasmoid, 500);
		}
	}
});
 
npDataEngine.sourceAdded.connect(function(name) {
	if (!source) {
		source = name;
		npDataEngine.connect(source, plasmoid, 500);
	}
});
 
if (source) {
	npDataEngine.connectSource(source, plasmoid, 500);
}

Parece complicado, comparado com os dois exemplos anteriores, mas só existe uma única coisa nova, e é isso que nós observando.
O mecanismo de dados nowplaying faz com que as fontes de dados apareçam e desapareçam assim como tocadores de mídia vem e vão. Esse plasmoid é um pouco estúpido, já que fica observando apenas a primeira fonte de dados da lista, mas isso é suficiente para nós agora.
A mágica é feita pela escuta dos signals sourceAdded e sourceRemoved do mecanismo de dados. Eles, sem surpresa, são emitidos quando a propriedade sources do mecanismo de dados, que lista todas as fontes conhecidas, muda. Note que é possivel sensivelmente requisitar fontes que não estão na propriedade source do mecanismo de dados – por exemplo, o mecanismo “twitter”  ter em teoria um número infinito de fontes disponivel, um para cada possivel conta do serviço twitter, e esses não podem ser listados pela propriedade source.

Desafio

Modificar o plasmoid.dataUpdate para exibir a informação que você quer, ao invés da informação fornecida pela nowplaying.

Monitor de sistema

Esse exemplo assume que você já está familiarizado com os exemplos anteriores como o NowPlaying (do qual esse exemplo foi adaptado).
O código abaixo ilustra como conectar-se ao systemmonitor e como receber atualizações de várias fontes de dados.
Você deve ter um plasmoid funcional dos exemplos anteriores. Pegue uma cópia do plasmoid e renomeio. Depois disso, pode substituir o arquivo contents/code/main.js pelo seguinte:

layout = new LinearLayout(plasmoid);
 
label = new Label();
layout.addItem(label);
label.text = "No connection";
 
// current values
var systemData = new Array();
 
function printData() {
    label.text = "";
    for (var name in systemData) {
	var data = systemData[name];
	label.text = label.text + name + ": ";
	for (var elt in data) {
	    label.text = label.text + " " + data[elt];
	}
	label.text = label.text + "\n";
    }
}
 
plasmoid.dataUpdated = function(name, data) {
    systemData[name] = data;
    printData();
};
 
smDataEngine = dataEngine("systemmonitor");
 
smDataEngine.sourceRemoved.connect(function(name) {
	// unsubscribe
	smDataEngine.disconnectSource(name, plasmoid);
    });
 
smDataEngine.sourceAdded.connect(function(name) {
	if (name.toString().match("^mem/physical")) {
	    // subscribe
	    smDataEngine.connectSource(name, plasmoid, 500);
	}
    });

Em smDataEngine.sourceAdded.connect() nós fornecemos uma função para se conectar ao signal. A conexão é chamada pelo monitor do sistema para cada fonte de dados. Nesse exemplo nós nos conectamos as fontes de dados da memória física.
Depois disso o plasmoid.dataUpdate() recebe atualizações. O valores recebidos são armazenados em arrays. O array é impresso em cada atualização.
Note que esse exemplo é bem ineficiente já que estamos chamando printData() para cada atualização. Uma maneira de otimizar isso é atualizar a caixa de texto apenas quando a fonte de dados “mem/physical/free”  for atualizada.

Traduzido de http://techbase.kde.org/Development/Tutorials/Plasma#Plasma_Programming_with_JavaScript