quinta-feira, 20 de novembro de 2008

Base64

De vez enquando, não tem como não, nós programadores sempre esbarramos na Base64.

Base64 é um protocolo de codificação que usa apenas seis bits, o que significa um conjunto de sessenta e quatro (64) elementos – daí Base64.

A conversão de oito (byte) para seis bits é feita da seguinte forma:
xxxxxx.xx xxxx.xxxx xx.xxxxxx


Ou ainda:
aaaaaabb bbbbcccc ccdddddd


Os elementos usados são caracteres simples, começando com as letras maiúsculas, A (0) a Z (25), então as letras minúsculas, a (26) a z (51), os números, 0 (52) a 9 (61), e os caracteres + (62) e / (63).

Na conversão de bytes (8b) para Base64 (6b), cada três bytes é convertido em quatro dígitos, então um código Base64 é sempre pensado em grupos de quatro. Se o tamanho de um código Base64 não for múltiplo de quatro, caracteres = são acrescentados ao final até que o tamanho seja múltiplo de quatro.

Python


Em Python, Base64 é tão simples que nem tem graça:
>>> print "Kodumaro".encode("base64")
S29kdW1hcm8=
>>> print "S29kdW1hcm8=".decode("base64")
Kodumaro


Lua


Em Lua, é preciso baixar o módulo Mime do LuaSocket:
> require "mime"
> print(mime.b64 "Kodumaro")
S29kdW1hcm8=
> print(mime.unb64 "S29kdW1hcm8=")
Kodumaro


Smalltalk


Sendo muito sincero sobre o assunto, não sei como fazer conversão de Base64 em Smalltalk. =(

Mas sei que os módulos do Seaside providenciam isso!

Se alguém souber, por favor informe!

[update 2009-11-27]
Sugestão do Hugo:

No Squeak tem isso aqui:
(Base64MimeConverter mimeEncode: 'base64' readStream) contents
(Base64MimeConverter mimeDecode: 'S29kdW1hcm8' as: ByteString) contents


E no Pharo tem métodos para strings:
'base64' base64Encoded
'S29kdW1hcm8' base64Decoded


A implementação usa as coisas do Squeak:
String>>base64Decoded
↑ (Base64MimeConverter mimeDecode: self as: self class)

String>>base64Encoded
↑ (Base64MimeConverter mimeEncode: self readStream) contents


Para outros Smalltalks eu não sei…

Legal né? =)

Muito legal sim, Hugo, valeu!
[/update]


C


Aha! Aqui começa de verdade a brincadeira!

É claro que você pode usar as funcionalidades de Base64 da gLibC, mas descobri que nem todas as versões dela apresentam tais funcionalidades:
#include <glib/gbase64.h>


Vamos precisar usar os cabeçalhos stdlib.h, string.h e sys/types.h. Como também vamos criar um cabeçalho para «compartilhar» algumas funções, ele também será incluído no início de nosso arquivo base64.c:
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include "base64.h"


Agora vamos criar um array com todos os possíveis caracteres Base64 em sua ordem natural:
static const char b64all[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef"
"ghijklmnopqrstuvwxyz0123456789+/";


Também vamos precisar de três funções locais: uma para obter o índice de um elemento (_getindex), outra para codificar para Base64 um grupo de três bytes (_encode) e mais uma para decodificar um grupo de quatro elementos Base64 (_decode):
int _decode(u_int8_t *, const u_int8_t *);
void _encode(u_int8_t *, const u_int8_t *, int);


Não é preciso uma função para _getindex:
#define _getindex(c) (int) (index(b64all, c) - b64all)


Repara que, em vez de char, estamos usando u_int8_t, que é mais conveniente quando queremos lidar com bytes enquanto bytes, não caracteres.

A partir daqui, se preferir, organize as funções em ordem alfabética – ou na ordem que quiser.

A primeira função que implementaremos será para codificar uma string C (const char *) para Base64. Como a string pode não ser bem formada, a função deverá receber também seu tamanho:
const char *b64encode(const char *original, int length) {
// Se o tamanho não for informado, consideramos uma string bem
// formada
if (length == 0)
length = strlen(original);

// Inteiro com o tamanho do código a ser gerado
int b64length = ((length + 2) / 3) * 4 + 1;

// Contadores para percorrer as strings
int i=0, j=0;

// Alocando memória para o código
char *b64 = (char *) malloc(sizeof(char) * b64length);
memset(b64, 0, b64length);

while (i < length) {
// Codifica um grupo de três bytes...
_encode(
(u_int8_t *) b64 + j,
(const u_int8_t *) original + i,
(length - i)
);

// E segue para o próximo grupo
i += 3;
j += 4;
}

// Retorna o código
return (const char *) b64;
}


A próxima função deve fazer o contrário, converter um código Base64 para uma string. Como a string resultante pode não ser bem formada – pode não ser terminada em carácter nulo ou possuir caracteres nulos no meio –, é preciso uma forma de informar seu tamanho, então ela receberá um ponteiro para um inteiro:
const char *b64decode(const char *b64, int *length) {
// Inteiro com o tamanho do código
int b64length = strlen(b64);

// Se não for múltiplo de quatro, há algo errado
if (b64length % 4 != 0)
return NULL;

// Tamanho máximo da string decifrada
int prob = (b64length / 4) * 3 + 1;

// Contadores para percorrer as strings
int i=0, j=0;

// Alocando memória para o resultado
char *s = (char *) malloc(sizeof(char) * prob);

while (j < b64length) {
// Decifra um grupo de quatro elementos
// e conta o resultado
i += _decode(
(u_int8_t *) s + i,
(const u_int8_t *) b64 + j
);

// Segue para o próximo grupo
j += 4;
}

// Se foi fornecido um inteiro para contagem, informa o tamanho
if (length != NULL)
*length = i;

// Retorna a string decifrada
return (const char *) s;
}


Agora precisamos das funções específica para as conversões.

Primeiro para codificar:
void _encode(u_int8_t *dest, const u_int8_t *src, int len) {
// Menor que 1, nada a fazer
if (len < 1)
return;

// Dados a serem retornados
int aux[] = { 0, 0, 0, 0 };

// Primeiro elemento: os 6 bits mais significativos do primeiro
// byte
aux[0] = src[0] >> 2;

// Segundo elemento: os 2 bits menos significativos do primeiro e
// os quatro bits mais significativos do segundo byte
aux[1] = (src[0] & 0x03) << 4;

if (len > 1) {
// SE houver um segundo...
aux [1] |= (src[1] & 0xf0) >> 4;

// Terceiro elemento: os quatro bits menos significativos do
// segundo e os dois mais significativos do terceiro byte
aux [2] = (src[1] & 0x0f) << 2;

if (len > 2) {
// Se houver um terceiro...
aux[2] |= src[2] >> 6;

// Quarto elemento: os seis bits menos significatos do
// terceiro byte
aux[3] = src[2] & 0x3f;
}
}

// Codifica agora os valores numéricos para string
dest[0] = b64all[aux[0]];
dest[1] = b64all[aux[1]];
dest[2] = '=';
dest[3] = '=';
if (len > 1) {
dest[2] = b64all[aux[2]];
if (len > 2)
dest[3] = b64all[aux[3]];
}
}

int _decode(u_int8_t *dest, const u_int8_t *src) {
// Representação numérica do código
int aux[] = { 0, 0, 0, 0 };

// Contador
int i, c = 1;

// Converte código para valores numéricos
for (i = 0; i < 4; ++i)
aux[i] = _getindex(src[i]);

// Primeiro byte: primeiro elemento seguido dos quatro bits mais
// significativos do segundo
dest[0] = (u_int8_t) (aux[0] << 2) | ((aux[1] & 0x30) >> 4);

// Zera os bytes seguintes
dest[1] = '\0';
dest[2] = '\0';

if (aux[2] != -1) {
// Se houver um terceiro elemento...
++c;

// Segundo byte: quatro bits menos significativos do segundo
// elemento seguidos pelos quatro bits mais significativos do
// terceiro
dest[1] = (u_int8_t) ((aux[1] & 0x0f) << 4) | (aux[2] >> 2);

if (aux[3] != -1) {
// Se houver um quarto elemento...
++c;

// Terceiro byte: dois bits menos significativos do
// terceiro elemento seguidos pelo quarto elemento
dest[2] = (u_int8_t)
((aux[2] & 0x03) << 6) |
aux[3];
}
}

// Retorna o tamanho da string
return c;
}


Cabeçalho


Por último criamos o cabeçalho base64.h, tornando públicas as duas funções de codificação e decodificação:
#ifndef _BASE64_H
#define _BASE64_H

const char *b64decode(const char *, int *);
const char *b64encode(const char *, int);

#endif


Conclusão


Mesmo que ninguém vá implementar uma biblioteca de conversão Base64, espero que este artigo sirva para ajudar a entender melhor do que se trata Base64.

[update]

Se quiser acrescentar algum açucar sintático, coloque em base64.c:
#ifdef C_PLUS_PLUS
const char *b64decode(const char *b64, int &length) {
return b64decode(b64, &length);
}
#endif


E mude o conteúdo de base64.h para:
#ifndef _BASE64_H
#define _BASE64_H

#ifdef C_PLUS_PLUS
extern "C" {
#endif

const char *b64decode(const char *, int *);
const char *b64encode(const char *, int);

#ifdef C_PLUS_PLUS
}
const char *b64decode(const char *, int &);
#else
#define _b64decode(s, len) b64decode(s, &len)
#endif

#endif

[/update]


[]'s
Cacilhas, La Batalema

quinta-feira, 13 de novembro de 2008

Objective C

GNUstep Escrevi recentemente um artigo com um exemplo bem simples de desenvolvimento de uma aplicação para ambiente GNUstep, mas cometi a gafe de não dedicar algum tempo falando da linguagem de programação em questão, Objective C.

Objective C, ou Objective-C, ou ObjC, é uma linguagem de programação reflectiva orientada a objetos criada nos idos da década de 1980 – mais ou menos na mesma época que o C++ – pelos fundadores da Stepstone, mais tarde adquirida pela NeXT. É na verdade uma camada bem fina sobre a linguagem C padrão.

Isso significa que, se você sabe programar em C, conseguirá programar em ObjC sem muito esforço.

Diferenças para C


Sobre a linguagem C, ObjC adiciona os seguintes recursos:
  • Orientação a objetos, adaptada de Smalltalk.
  • Tipo boleano – BOOL. Verdadeiro é YES e falso é NO.
  • Comentário de linha, iniciado por // (na época C não suportava).
  • A diretiva de preprocessador #import, que funciona como #include, mas só inclui o cabeçalho se ele não foi incluído ainda.
  • O tipo id, similar a void * de C/C++.
  • A constante nil, que é um objeto id sem valor: (id)0.


A maior parte dos comandos referentes a orientação a objetos começam com @, como @interface, @implementation, @end, @private, @protected, @public, @protocol, etc.… Entre colchetes é avaliado o ambiente de mensagem, com uma sintaxe muito parecida com a de Smalltalk.

Criando uma classe


Como exemplo, vamos criar a classe Point, com os atributos x e y.

Para tanto, primeiro criamos a interface no arquivo Point.h:
#ifndef _POINT_H
#define _POINT_H

#import <Foundation/Foundation.h>

@interface Point : NSObject
{
int x;
int y;
}

+ (id) newWithX: (int)valuex Y: (int)valuey;
- (id) initWithX: (int)valuex Y: (int)valuey;
- (int) diagonal;
- (int) getX;
- (int) getY;
- (void) setX: (int)value;
- (void) setY: (int)value;

@end

#endif


As diretivas #ifndef, #define e #endif já são velhas conhecidas dos programadores C/C++. O #import já foi explicado, funciona parecido com #include.

O cabeçalho Foundation/Foundation.h inclui a classe NSObject, definida pela API OpenStep, usada aqui como classe pai de nossa classe Point.

O comando @interface inicia a descrição da interface e o comando @end encerra. O pequeno escopo entre chaves define os atributos de instância, protegidos por padrão – para defini-los como públicos a sintaxe seria:
{
@public
int x;
int y;
}


As linhas começadas por um sinal de menos (-) indicam métodos de instância – métodos de classe são iniciados por +. Por exemplo:
- (id) initWithX: (int)valuex Y: (int)valuey;

Significa um método de instância que recebe dois atributos inteiros – valuex e valuey, e retorna um id.

Agora precisamos implementar a interface. Para isso vamos criar o arquivo Point.m:
#include <math.h>
#import "Point.h"

@implementation Point

+ (id) newWithX: (int)valuex Y: (int)valuey {
return [[Point alloc]
initWithX: valuex Y: valuey];
}

- (id) initWithX: (int)valuex Y: (int)valuey {
if ((self = [super init])) {
x = valuex;
y = valuey;
}
return self;
}

- (int) diagonal {
return (int) sqrt((double) ((x * x) + (y * y)));
}

- (int) getX {
return x;
}

- (int) getY {
return y;
}

- (void) setX: (int)value {
x = value;
}

- (void) setY: (int)value {
y = value;
}

@end


O código é muito parecido com C, e até mesmo com C++, cabendo apenas alguns comentários:
  • self é uma palavra reservada que representa a instância, como this de Java e C++.
  • super referencia a classe pai, como em Java.
  • Repare no comando [super init]: aqui é passada uma mensagem para super e o valor retornado é atribuído a self.


Para instanciar um objeto, a sintaxe é:
id point = [Point newWithX: 4 Y: 5];


Ou, com tipagem estática:
Point *point = [Point newWithX: 4 Y: 5];


Para obter o valor de x:
printf("x = %d\n", [point getX]);


E para mudar seu valor:
[point setX: 2];


OpenStep


OpenStep é a API aberta para desenvolvimento de aplicações para NEXTSTEP, resultado de um trabalho colaborativo da NeXT com a SUN Microsystems.

Diversos sistemas e ambientes oferecem suporte a OpenStep, como OS X e GNUstep.

Diversos recursos interessantes são oferecidos nessa API, desde vantagens semelhantes à STL de C++, até conjunto de widgets para construção de janelas, toolkit.

Já vimos uma classe dessa API, NSObject. Outra interessante é NSString, cuja finalidade é a mesma de std::string de C++ e String de Java.

A criação de uma NSString é:
id url = [NSString stringWithCString: "http://kodumaro.blogspot.com/"];


Há ainda um apelido sintático:
id url = @"http://kodumaro.blogspot.com/";


Um forma legal de conhecer mais sobre OpenStep é ler os manuais de referência do GNUstep (toolkit aqui) e do OS X.

[]'s
Cacilhas, La Batalema

sexta-feira, 7 de novembro de 2008

Desenvolvendo aplicações GNUstep

GNUstep Sempre fui fascinado pelo NeXTSTEP e gostaria muito tivesse dado certo. Por mais que a Apple negue, seu Mac OS X não é muito mais do que uma versão nova de um NeXTSTEP portado para equipamentos MacIntosh.

Mas não só o OS X herdou os recursos do NeXTSTEP. No começo da década de 1990, em colaboração com a SUN Microsystems a NeXT lançou o OpenStep, uma especificação aberta que define a API para o NeXTSTEP.

Assim ambientes baseados em NeXTSTEP podem rodar em quaisquer sistemas operacionais e aplicações portáteis podem ser criadas.

A Free Software Foundation lançou um projeto de implementação da API OpenStep para sistemas GNU, o GNUstep.

Mais do que uma API de programação, GNUstep é todo um ambiente, desde a API até aplicações e ambiente operacional.

Inicialmente o ambiente gráfico / geranciador de janelas era o AfterStep. Certo dia, na lista de discussão para definir os rumos da versão 2 do AfterStep, um brasileiro chamado Alfredo Kojima sugeriu a reescrita do código do zero.

Enquanto o grupo continuava debadendo, Kojima decidiu iniciar o desenvolvimento da versão 2 do AfterStep, codinome WindowMaker, por conta própria e depois apresentou-o à comunidade. O Window Maker então substituiu o AfterStep como gerenciador de janelas do projeto GNUstep e o AfterStep continuou seu desenvolvimento a parte.

Diversas versões do Window Maker foram lançadas, apesar de nunca ter chegado a uma versão 1.0, um eterno beta – atualmente se encontra na versão 0.92. Porém seu desenvolvimento esfriou gradativamente.

Ano passado o projeto voltou a esquentar e promessas de novas versões estão no ar, o que me inspirou a escrever este artigo.

Consulte o blog do Bardo.


GNUstep


Mas este artigo não é sobre Window Maker, e sim sobre GNUstep.

Para desenvolver aplicações GNUstep, você precisa baixar seu core antes. Acesse a página de recursos, vá ao tópico GNUstep Core e baixe as versões estáveis de cada pacote. Descompacte, compile e instale na ordem em que os pacotes aparecem na tabela (startup, make, base, gui e backend). O core do GNUstep deverá ser instalado em /usr/GNUstep/.

Você precisa acrescentar /usr/GNUstep/System/Tools a seu path de executáveis, /usr/GNUstep/System/Library/Libraries a sua lista de diretórios de bibliotecas – em ambiente GNU/Linux, acrescente esse path ao arquivo /etc/ld.so.conf e execute ldconfig – e crie uma variável de ambiente GNUSTEP_MAKEFILES com o valor /usr/GNUstep/System/Library/Makefiles e outra GNUSTEP_INSTALLATION_DOMAIN com o valor SYSTEM.

Tendo o GNUstep instalado, podemos partir para as ferramentas de desenvolvimento, Gorm e Project Center. Você também encontrará apontadores para as versões estáveis na mesma máquina de recursos, no tópico GNUstep Development Tools.

Criando um projeto


Vamos criar nosso primeiro projeto GNUstep!

Primeiro abrimos o Project Center com o comando:
bash$ openapp ProjectCenter


Vá em ProjectNew... (ou pressione M-n), vai abrir uma janela de diálogo pedindo o nome do projeto. Selecione o tipo Application e forneça o nome Temperature e clique Ok.

Por enquanto nossa brincadeira com Project Center acaba por aqui. Pode salvar – ProjectSave ou M-s – e sair – Quit ou M-q.

Criando uma aplicação


Agora vamos trabalhar no Gorm, execute:
bash$ openapp Gorm


Um segredinho: nas versões anteriores você mandava abrir o Temperature.gorm a partir do Project Center e ele já abria o Gorm para a aplicação atual. Na versão que experimentei – Gorm 1.2.6 e Project Center 0.5.0 – há um bug. O truque é abrir o Gorm a parte e criar uma nova aplicação.
Se quiser testar se está funcionando – em algumas lugares que testei funcionou – para abrir o Gorm, na janela principal do Project Center clique em Interfaces e duplo clique em Temperature.gorm.

No Gorm, crie uma janela e marque como Visible at launch time.


No Gorm então acesse DocumentNew Application, depois vá em DocumentSave As.... Na janela de diálogo encontre seu projeto Temperature, dentro dele Resources e, dentro desse, salve como Temperature.gorm – confirme o Replace.

Vão abrir três janelas: Main Menu, My Window e Temperature.gorm. Se as janelas Palettes e Inspector não estiverem abertas, encontre-as no menu Tools.

Selecione a janela My Window – ela representa a janela principal da aplicação – e veja em Inspector o campo Title contendo My Window – se não estiver vendo, mude o seletor para Attributes. Mude o título para Temperature Converter – repare que o título da janela My Window também mudou.

Repare que na janela Palettes tem alguns widgets. Para acrescentar um widget à janela é só segurar e arrastar (drag'n'drop). Vamos acrescentar três tipos de widget: Text, System Bold e Button. Adicione componentes até ficar mais ou menos com a cara deste corte:
screenshot 1

Para mudar o texto de um widget, use o duplo clique. Você também pode mudar a borda no Inspector. Altere os widgets até ficarem mais ou menos assim:
screenshot 2

Agora é hora de criar a classe para gerenciar essa janela! Hora de codar? Ainda não…

Na janela Temperature.gorm, na barra principal clique em Classes e selecione NSObject – futuca um pouco que você acha. No menu Operations selecione SubClass. Na lista da direita vai aparecer NewClass e o Inspector vai selecioná-lo – veja a caixa Class com NewClass. Mude o nome da classe para ConverterManager. Apenas confirme na janela Modifying Class.

Podemos agora criar os atributos (outlets) e métodos da classe (actions).

Vamos criar um atributo para cada caixa de texto que criamos – incluside a de Kelvin.

Na janela Inspector há a aba Outlets (0) com o botão Add. Clique nesse botão e teremos NewOutlet com um duplo clique você será capaz de mudar seu nome para degreeC.

Crie mais dois atributos: degreeF e kelvin.

Para criar o primeiro método acesse a aba Actions (0) e siga o mesmo procedimento anterior. Os métodos serão chamados convertC2F: e convertF2C:.

E a classe já está pronta!

Instanciando a classe


Mantendo a classe ConverterManager, no menu principal do Gorm selecione ClassesInstantiate. Na janela Temperature.gorm aparecerá um objeto ConverterManager: é agora que vem parte da mágica…

Precisamos inicialmente ligar o atributo degreeC dessa instância à primeira caixa de texto da janela (ao lado do rótulo °C).

Segure a tecla Ctrl, clique sobre a instância, segure e arraste, solte sobre a caixa de diálogo – vai aparecer um T na caixa –, então, na janela Inspector selecione degreeC e clique no botão Connect. Vai aparecer um S sobre a instância e, na janela Inspector, em Connections, vai aparecer: degreeC (TextField(0)).

Faça o mesmo para as outras caixas ligando cada caixa a um atributo.

Agora precisamos fazer com que os botões acionem métodos da instância, para isso o procedimento é o inverso: segurando a tecla Ctrl clique sobre o botão Celsius to Fahrenheit, segure, arraste e solte sobre a instância de ConverterManager na janela Temperature.gorm.

Na janela Inspector clique em targetconvertC2F: e então clique no botão Connect.

Faça o mesmo para conectar o outro botão ao outro método. Agora todas as ações estão conectadas!

Salvando tudo


Na janela Temperature.gorm, selecione Classes e encontre a classe ConverterManager. Selecione-a, então, no menu principal do Gorm clique em ClassesCreate Class Files. Apenas confirme as duas janelas de diálogo. Isso criará os dois arquivos ConverterManager.m e ConverterManager.h dentro de Resources.

Agora clique em DocumentSave All e Quit.

De volta ao Project Center


Volte ao Project Center e clique em ProjectOpen... e abra o arquivo PC.project do projeto Temperature.

Na janela principal do projeto dê um duplo clique em Classes e depois em ConverterManager.m, isso adicionará o código ao projeto do Project Center.

Finalmente o código!


Precisamos finalmente editar o conteúdo dos métodos. Duplo clique sobre ConverterManager.m na janela principal e vai abrir a janela do editor:
/* All Rights reserved */

#include <AppKit/AppKit.h>
#include "ConverterManager.h"

@implementation ConverterManager


- (void) convertC2F: (id)sender
{
/* insert your code here */
}


- (void) convertF2C: (id)sender
{
/* insert your code here */
}

@end


O código é Objective C, um variante orientado a objetos de C que lembra um pouco Smalltalk.

O método covertC2F deve obter o valor do atributo degreeC – que está ligado à caixa de diálogo com o valor em graus Celsius –, calcular e ajustar os valores em Fahrenreit e Kelvin:
- (void) convertC2F: (id)sender
{
float celsius = [degreeC floatValue];
float fahrenreit = (9.0f * celsius / 5.0f) + 32.0f;

[degreeF setStringValue: [NSString stringWithFormat: @"%1.2f",
fahrenreit]];

[kelvin setStringValue: [NSString stringWithFormat: @"%1.2fK",
(celsius + 273.15f)]];
}


O método convertF2C deve fazer o contrário:
- (void) convertF2C: (id)sender
{
float fahrenreit = [degreeF floatValue];
float celsius = 5.0f * (fahrenreit - 32.0f) / 9.0f;

[degreeC setStringValue: [NSString stringWithFormat: @"%1.2f",
celsius]];

[kelvin setStringValue: [NSString stringWithFormat: @"%1.2fK",
(celsius + 273.15f)]];
}


Salve agora (M-s) e feche o editor. Clique no ícone com a chave de fenda para compilar:
Build

Na janela de build há outro ícone igual, clique nele para compilar. Se tudo correr bem você verá uma bela mensagem:
=== Build succeeded! ===


Se falhar, volte e veja o que está errado.

Clique então no ícone do foguete para rodar:
Launch

Na janela Launch clique no mesmo ícone e a janela irá abrir. Faça alguns testes para ver se funciona bem.

Para sair clique com o botão direito e escolha Quit.

Se tudo funcionar bem, seu programa está pronto! Salve tudo em ProjectSave.

Instalando a aplicação


Para instalar, abra um terminal:
bash$ cd Temperature/
bash$ make
bash$ sudo make install


Assim sua aplicação estará instalada em /usr/GNUstep/System/Applications/Temperature.app – ou /usr/GNUstep/Local/Applications/Temperature.app, se você se esqueceu de ajustar o conteúdo da variável de ambiente GNUSTEP_INSTALLATION_DOMAIN – e você executá-la com o comando:
bash$ openapp Temperature


Conclusão


Espero que com este artigo algumas pessoas se interessem pelo desenvolvimento de aplicações para ambiente GNUstep, que aliás roda sobre Mac OS X também – em plataforma OS X, o WINGs (toolkit de widgets para Window Maker) e o GNUstep são abstrações do Cocoa, toolkit de widgets do OS X.

A facilidade de desenvolvimento de aplicações gráficas para GNUstep com Project Center e Gorm é incrível, apesar de seu jeitão esquisito e do uso da tremendamente estranha linguagem de programação Objective C.

[]'s
Cacilhas

PS: Eu já estava morrendo de sono quando escrevi este artigo, portanto alguns erros de português podem ocorrer. Por favor me avisem!