venerdì 20 dicembre 2013

LifeGame, il gioco della vita

In realtà erano anni che mi frullava per la testa l'idea di creare una mia versione del gioco della vita, ma per un motivo o per un altro, non l'avevo mai sviluppato.
Però ultimamente sono stato preso dalla documentazione ufficiale del progetto GNOME, e sono venuto a conoscere una libreria di nome clutter la quale viene descritta nel progetto come “una libreria per creare animazioni e interfacce grafiche eleganti”; mi sono così incuriosito e ho dato un'occhiata alla documentazione ufficiale di questa libreria: non mi ci è voluto molto a capire che segue lo stesso stile di GTK+, usa le sue stesse librerie (GObject, GLib, GDK ecc.), è integrata nell'ambiente GNOME e usa le librerie OpenGL.
Così, mani sulla tastiera, ho cominciato a sviluppare con clutter il solitario che ho sviluppato circa due anni fa per windows e creare una versione giocabile, ma il progetto è lungo e continua ancora adesso.
Quindi ho deciso di usare clutter per sviluppare il gioco della vita, che è più veloce e meno impegnativo: 4 giorni e ho implementato la versione 0.8, in cui oltre a usare clutter, mi sono avvalso della libreria clutter-gtk la quale fa da “ponte” per le librerie clutter e gtk.

Più avanti ne approfitterò per avviare un mini-tutorial su clutter descrivendo il codice sorgente del gioco della vita.

Il gioco della vita

Il gioco della vita è un automa cellulare che come il nome stesso dice, simula alcuni comportamenti assimilabili alla vita, alla crescita della popolazione e così via. Personalmente mi piace immaginarlo come popolazioni di umani su una terra che si stanziano (forme stabili) o vagano (forme dinamiche) su un territorio, dove possono entrare in guerra fra loro (collisioni) o convivere pacificamente, oppure ancora dove possono prosperare (automi a crescita infinita), o morire (configurazioni che scompaiono dopo tot-generazioni).
Dopo aver confuso le idee del lettore che non conosce il gioco della vita, passo a spiegare come funziona lo stesso. Esso si basa su uno spazio costituito da celle, dove ogni cella può essere accesa (viva) o spenta (morta). La configurazione in cui si trova lo spazio funziona da input per la generazione seguente, la quale funzionerà da input per la generazione seguente ancora e così via: esso è Turing-compatibile, infatti è possibile configurare le celle in una maniera particolare costruendo porte logiche e implementare istruzioni.
Le regole che determinano il passaggio da una generazione a un'altra sono essenzialmente due:
  1. Se una cella è morta, essa nasce se ha esattamente 3 celle vive accanto
  2. Se una cella è viva, essa muore se ha un numero di celle accanto diverso da 2 o da 3, per isolamento o sovraffollamento
Queste semplici regole permettono di creare un gran numero di configurazioni, elencate qui appresso.
  • Forme stabili:
    • Blocco
    • Barca
  • Oscillatori:
    • Lampeggiatore
    • Rospo
  • Nav icelle:
    • Aliante
    • Astronave
  • Altro:
    • Cannone di alianti
    • Die hard

Il software

Il software che ho sviluppato è minimale, usa clutter ma non usa appieno le sue potenzialità (probabilmente avrei potuto sviluppare il gioco anche col solo uso di gtk).
Si presenta con la barra degli strumenti in alto, lo stage al centro, e la barra di stato in basso per il conteggio delle generazioni:
La scena del gioco con un oscillatore e un aliante

La barra degli strumenti:
La barra degli strumenti

contiene i comandi per:
  • Creare una nuova scena
  • Aprire un file
  • Salvare un file
  • Mettere in pausa/riprendere
  • Le impostazioni
  • Modificare il ritardo (in ms) tra una generazione e un'altra
  • Informazioni sul programma
Il supporto per i file è in realtà un modo per salvare delle configurazioni particolari (per esempio salvare una navicella), ma devo ancora perfezionarlo perché non è molto utile (potrebbe essere utile se riuscissi a importare nella scena il contenuto del file in una determinata posizione della scena stessa).

Installazione

Innanzitutto, per ora è disponibile solo per linux. Purtroppo per varie difficoltà nel compilare i sorgenti di clutter e clutter-gtk su windows, non posso andare avanti (fortunatamente il team gtk+ fornisce già i binari compilati per windows).

È possibile installare il software in due modi:
1)Compilando i sorgenti
2)Installando il pacchetto .deb per i sistemi debian/ubuntu


Per compilare i sorgenti è necessario avere installati sul sistemai seguenti pacchetti:
  • pkg-config
  • libgtk-3-dev
  • libclutter-1.0-dev
  • libclutter-gtk-1.0-dev
    quindi basta scaricare ed estrarre in una cartella il contenuto del file lifegame-0.8-src.tar.gz, aprire il terminale, posizionarsi sulla cartella con i file estratti:
cd [percorso-cartella]
e lanciare il comando:
sudo ./install
il quale lancia il file di script “install” che ho preparato il quale compila i sorgenti e installa il software nel sistema. Per i sistemi che hanno GNOME o simili, come ubuntu, è possibile trovarlo nelle applicazioni sotto la categoria “giochi”, altrimenti è possibile lanciarlo da terminale col comando “lifegame”.

Per installare direttamente i binari è possibile scaricare il comodo pacchetto .dev (per i sistemi con pacchettizzazione debian, come Debian stesso e Ubuntu) che risolve automaticamente le dipendenze. Per installare il pacchetto .deb su ubuntu basta aprirlo con un doppio click oppure digitare da terminale:

dpkg --install lifegame-0.8.deb

Download:

giovedì 12 dicembre 2013

Linux: come riprodurre un'onda sonora tramite l'interfaccia ALSA

Un'onda sinusoidale PCM
Qualche tempo fa (= due anni fa :D) vi ho mostrato come riprodurre un'onda sonora tramite l'interfaccia WaveOut di Windows, oggi vi mostrerò come riprodurre un'onda sonora su Linux tramite l'interfaccia ALSA, la libreria di basso livello divenuta uno standard de facto per il sonoro su Linux, tant'è che oggi viene distribuita col kernel di Linux direttamente.

Cos'è ALSA?

ALSA (Advanced Linux Sound Architecture) è un modulo del kernel di linux, ovvero un programma che estende le funzionalità del kernel. Per la sua vicinanza al kernel, è molto usata dalle applicazioni che vogliono comunicare direttamente con la scheda audio, anche se esistono molte altre comode librerie (PulseAudio, Gstreamer) che si interfacciano a loro volta ad ALSA ma che semplificano di molto la vita ai programmatori.

Prima dell'avvento di ALSA (che supporta anche le periferiche MIDI) esisteva un altro “standard” su linux, OSS (Open Sound System), il quale aveva la particolarità di permettere al programmatore di comunicare direttamente con la scheda audio tramite il device a caratteri (un file che, nei sistemi Unix, comunica coi driver di un qualsiasi dispositivo) /dev/dsp, la sua forza stava nel fatto di scrivere dati su questo file come se fosse un qualunque altro file, e di leggerli: la prima operazione riproduceva un sonoro, la seconda permetteva la registrazione. Oggi OSS non viene più distribuito su Ubuntu e il device a caratteri /dev/dsp non c'è più fisicamente, ma se non sbaglio (devo informarmi di più su questo argomento) ALSA dovrebbe mantenere una retrocompatibilità con OSS.

Setting up ALSA (preparazione)

Per utilizzare ALSA dobbiamo scaricare i file di sviluppo (header e librerie), e quindi dobbiamo installare il pacchetto libasound2-dev scaricando i sorgenti dal sito ufficiale e compilando oppure, più semplicemente, su Debian Ubuntu andare sul terminale e scrivere:
sudo apt-get install libasound2-dev
che farà tutto lui. Per compilare un'applicazione ALSA (tipo quella che verrà trattata in questo post) basta compilare da terminale scrivendo:
gcc esempio.c -o esempio -lasound
quindi lanciare il programma compilato con:
./esempio
Volendo si può compilare anche con pkg-config se l'avete configurato, ma visto che ALSA non vuole linkate molte librerie il suo uso anzi è più complicato che scrivere solamente -lasound.
  • Se avete installato il pacchetto con apt-get (come la riga sopra) allora avrete i file nei posti giusti e il compilatore troverà sia l'header che noi indichiamo nel file .c, che la libreria lasound
  • Se avete compilato e installato manualmente dovrete indicare a gcc sia la cartella dell'header di alsa che la libreria libasound2
A questo punto possiamo passare al codice.

Un po' di codice

Il codice è veramente breve e semplicissimo se avete già letto il vecchio post che spiega come creare l'onda sinusoidale. Creare l'onda è la difficoltà maggiore, perché una volta stabiliti i parametri del file wave, le funzioni di ALSA sono molto intuitive. Riporto per intero il codice:
#include <stdio.h>
#include <alsa/asoundlib.h>


typedef enum {
TIME = 5, // Durata della traccia (secondi)
CHANNELS = 1, // Numero dei canali
SAMPLE_RATE = 44100, // Frequenza di campionamento (Hz)
BITS_PER_SAMPLE = 8, // Bits per campione
BLOCK_ALIGN = CHANNELS * BITS_PER_SAMPLE / 8, // Allineamento del blocco
BYTES_PER_SECOND = SAMPLE_RATE * BLOCK_ALIGN, // Frequenza di campionamento in Hz
SAMPLES = CHANNELS * SAMPLE_RATE * TIME, // Numero di campioni richiesti per la durata scelta
} WAVE;




int main() {
/*
* Creo puntatore con l'onda sinusoidale
*/
char* ptr;
int tone = 440; //440 Hertz
float volume = 10.0f;
float pulsazione = ( 2.0f * 3.14f / (float) SAMPLE_RATE / CHANNELS ) * (float) tone;
int i;
if ((ptr = (char*)malloc(SAMPLES))==NULL){
fprintf (stderr,"Si e' verificato un errore!\n");
return EXIT_FAILURE;
}
for (i=0 ; i < SAMPLES; i++ )
ptr[i] = (char) ( cos( (float) i * pulsazione ) * volume + 128.0f );
/*
* Inzializzo ALSA
*/
snd_pcm_t *handle; //handle del device
int err;
err = snd_pcm_open(&handle, "default", SND_PCM_STREAM_PLAYBACK, SND_PCM_ASYNC); //apro il device
if (err<0){
fprintf (stderr,"Si e' verificato un errore!\n");
return err;
}
err = snd_pcm_set_params(handle,SND_PCM_FORMAT_U8 ,SND_PCM_ACCESS_RW_INTERLEAVED,CHANNELS,SAMPLE_RATE,0,0);
if (err<0){
fprintf (stderr,"Si e' verificato un errore!\n");
return err;
}
/*
* invio dati
*/
snd_pcm_writei(handle,ptr,SAMPLES);
snd_pcm_close(handle);
}


Con l'istruzione
err = snd_pcm_open(&handle, "default", SND_PCM_STREAM_PLAYBACK, SND_PCM_ASYNC);
assegniamo apriamo il device sonoro di default, conservando il suo handle in “handle”, specificando che apriamo il device per la riproduzione (SND_PCM_STREAM_PLAYBACK), in maniera asincrona (SND_PCM_ASYNC) anche se non ce ne facciamo niente del fatto che lo apriamo in maniera asincrona perché dovremmo specificare la funzione di callback per ricevere asincronicamente dei segnali (non è argomento di questo post).
Con l'instruzione
err = snd_pcm_set_params(handle,SND_PCM_FORMAT_U8 ,SND_PCM_ACCESS_RW_INTERLEAVED,CHANNELS,SAMPLE_RATE,0,0);
settiamo i parametri necessari per la riproduzione sul device “handle” in particolare gli diciamo che il formato dei samples PCM è unsigned 8 bit (ovvero il char), specificando il modo di accesso (SND_PCM_ACCESS_RW_INTERLEAVED, adatto per quello che dobbiamo fare), il numero di canali, la frequenza di campionamento, se ALSA deve ricampionare i campioni (0 per NO, 1 per SI), e la latenza della scheda audio, che supponiamo in questa sede che sia nulla.
Per l'invio dei campioni al device usiamo:
snd_pcm_writei(handle,ptr,SAMPLES);
dove specifichiamo il device (handle), il puntatore ai campioni (ptr) e il numero di sample da inviare (SAMPLES).


Rimando a un'altra volta (magari fra due anni :P) lo sfruttamento della modalità asincrona.

Fine.

martedì 10 dicembre 2013

I comandi più divertenti di linux

Ci sono alcuni comandi (=programmi) che ho scoperto su linux che non sono altro che comandi creati a scopo goliardico, senza alcuna utilità se non quella di strapparci un piccolo sorriso. Possiamo vederli qui appresso, ordinati iniziando da quelli che considerò più divertenti.

1. sl

Il comando ls su linux ("list) serve per elencare i file contenuti in una cartella, senza opzioni elenca i file contenuti nella cartella corrente. Essendo un comando molto usato, può capitare che nella fretta qualcuno scriva "sl" invece che "ls", causando un simpatico effetto: un treno che passa dalla finestra del terminale. 

Normalmente nelle distro non è installato, ma su Debian/Ubuntu per installarlo basta scrivere sul terminale:
sudo apt-get install sl

2. xcowsay

 Questo comando non fa altro che lanciare una applicazione GTK+ che visualizza una mucca che "dice" quello che noi le passiamo come argomento. 

Per installarla lanciamo:
sudo apt-get install xcowsay

3. figlet & toilet

Due programmi analoghi che stampano usando l'ASCII art quello che noi passiamo come argomento. 

Per installarli:
sudo apt-get install figlet
sudo apt-get install toilet

4. oneko

Per i gattofili c'è una simpatica applicazione che in effetti potete lanciare e lasciarla in background. Mostra un gattino che segue sempre il vostro mouse, (in realtà il puntatore del mouse):
Per installarlo:
sudo apt-get install oneko
Mentre per lanciarlo e lasciarlo in background:
oneko &
Poi premete INVIO e scrivete "exit" per uscire dal terminale senza chiudere l'applicazione.

5. moo

Rimanendo sempre in tema mucca, visto che fa molto simpatia, ecco quest'altra chicca: in realtà è un easter-egg di apt-get, sistema di pacchettizzazione di debian. Basta scrivere:
apt-get moo

6. fortune

Questa è una vecchia applicazione per Unix, quindi presente anche in Linux oggi. Mostra un messaggio pseudo-casuale preso da un database di citazioni. Pesa circa 2 megabyte, per installarlo:
sudo apt-get install fortune



martedì 26 novembre 2013

Gnome Launcher Creator, piccola utility per creare lanciatori per GNOME

Ho scritto in giornata questa piccola utility che permette di creare dei lanciatori per GNOME in maniera facile, appoggiandosi all'utility per GNOME gnome-desktop-item-edit.

Potete scaricare il sorgente da QUI oppure (e cio è molto triste perché ricorda windows) scaricare direttamente l'eseguibile da QUI.  Se lo dovete compilare assicuratevi di avere installato il pacchetto libgtk-3-dev necessario per le librerie GTK+,  e se avete configurato anche pkg-config, vi basta eseguire da terminale il comando

make 

sulla cartella contenente il file Makefile per compilare direttamente e creare l'eseguibile GLC.

lunedì 18 novembre 2013

Calcolare il determinante di una matrice di qualsiasi ordine in maniera ricorsiva - Software grafico GNU/Linux

Come avevo promesso, ecco la versione GNU/Linux del programma grafico per calcolare il determinante che ho presentato nel precedente post (a dir la verità ci ho fatto qualche lieve modifica)


A differenza della versione per Windows, su Linux con molta probabilità avrete già le librerie installate sul sistema, quindi ecco a voi solo l'eseguibile (per versioni di GTK > 3.0):


martedì 12 novembre 2013

Calcolare il determinante di una matrice di qualsiasi ordine in maniera ricorsiva - Software grafico Windows

Tempo fa ho scritto un post che descriveva il metodo per calcolare il determinante di una matrice in maniera ricorsiva (metodo di Laplace) e mostravo la funzione creata per C++ per calcolare il determinante data una matrice quadrata di un qualsiasi ordine. 

Ho creato un'interfaccia grafica scritta con GTK+ dove si può comodamente inserire la matrice di cui si vuole calcolare il determinante. Per ora il programma è compilato con windows perché in questo momento non ho un linux a portata di mano, quindi l'eseguibile che potrete scaricare è per windows. 

Il programma inizia con un prompt, chiedendo all'utente di inserire l'ordine della matrice:
subito dopo, inserendo un ordine di 3 per esempio, avremo la schermata:

in cui potremmo inserire i valori e calcolare il determinante:

Il programma non è limitato a un certo ordine, teoricamente si può inserire qualsiasi ordine, salvo tempi che si allungano per via dell'elaborazione prima della parte grafica, poi (più veloce) la parte computazionale. I problemi derivati dall'uso di GTK+ su windows è che queste librerie sono nate in ambiente linux, e il porting per windows è complesso e macchinoso rispetto altre librerie grafiche. Ma io sono affezionato a Ubuntu e Gnome :)

Lo potete scaricare da qui (windows):
Istruzioni:
estraete i files in una cartella e lanciate "Determinante.bat", il quale lancerà a sua volta "Determinante.exe" contenuto nella cartella "lib". Ho dovuto fare questo trucchetto perché il programma per funzionare ha bisogno delle librerie GTK+.

A breve anche la versione per linux.

domenica 10 novembre 2013

Memory, gioco mnemonico che ho scritto in javascript



Aggiornamento: clicca qui

Ispirandomi al gioco che era presente nel vecchio Nokia 3210, ho creato questo gioco con poche linee di codice in Javascript e qualcosa basilare di CSS e HTML. Il file HTML su cui mi baso (ci sono riferimenti alla tabella, id in particolare, e un tasto per "nuovo gioco"):









<CENTER>
<p style="font-size:x-large;font-style:italic;">Memory: clicca sulle caselle e memorizzale per trovare le coppie, fino a scoprire tutte le caselle.</p>
<a href="#">
<table>
<tr><th id="11" onclick="clickTo(1,1)">*</th><th id="12" onclick="clickTo(1,2)">*</th><th id="13" onclick="clickTo(1,3)">*</th><th id="14" onclick="clickTo(1,4)">*</th></tr>
<tr><th id="21" onclick="clickTo(2,1)">*</th><th id="22" onclick="clickTo(2,2)">*</th><th id="23" onclick="clickTo(2,3)">*</th><th id="24" onclick="clickTo(2,4)">*</th></tr>
<tr><th id="31" onclick="clickTo(3,1)">*</th><th id="32" onclick="clickTo(3,2)">*</th><th id="33" onclick="clickTo(3,3)">*</th><th id="34" onclick="clickTo(3,4)">*</th></tr>
<tr><th id="41" onclick="clickTo(4,1)">*</th><th id="42" onclick="clickTo(4,2)">*</th><th id="43" onclick="clickTo(4,3)">*</th><th id="44" onclick="clickTo(4,4)">*</th></tr>
</table>
<input type="button" value="Nuovo gioco" onclick="nuovoGioco()" style="width:100px;height:50px;">
</a>
</CENTER>
Mentre  il cuore del programma vero e proprio è lo script in javascript, tutto commentato:
<script type="text/javascript">
//Array per contenere i caratteri da scoprire
var array = new Array();
//Array per controllare il flusso del gioco (se sono state aperte due caselle e sono uguale allora scopri, altrimenti nascondi ecc.
var caselle_aperte = new Array();
/*INIZIO*/
nuovoGioco();

/*
*Questa funzione gestisce quando viene cliccata una casella, e decide cosa fare
*a seconda del contenuto di caselle_aperte
*/

function clickTo(riga,colonna){
   //Caso 1: non ci sono caselle aperte, quindi deve scoprirne una sola
   if (caselle_aperte.length==0){
      mostraSimbolo(riga,colonna);
      caselle_aperte[0]=riga.toString()+colonna.toString();
      return;
   }
   //Caso 2: c'è già una casella aperta, scopri la nuova e controlla se sono uguali
   if (caselle_aperte.length==1){
      //se è la stessa ignora ed esci
      if (caselle_aperte[0]==riga.toString()+colonna.toString())
   return;
     //scopri la casella
      mostraSimbolo(riga,colonna);
      //memorizza la nuova casella in caselle_aperte
      caselle_aperte[1]=riga.toString()+colonna.toString();
     //controlla se i simboli sono uguali
      if (getSimboloAt(caselle_aperte[1])==(getSimboloAt(caselle_aperte[0]))){
          document.getElementById(riga.toString()+colonna.toString()).setAttribute('style','background-color:#81F781;');
          document.getElementById(caselle_aperte[0]).setAttribute('style','background-color:#81F781;');
          //cancella il contenuto di caselle_aperte
          caselle_aperte.pop();
          caselle_aperte.pop();
          //Controlla se hai vinto (cioè se tutte le caselle sono state scoperte
          var conta;
          conta=0;
          for (i=1;i<=4;i++)
             for (c=1;c<=4;c++){
                 if (document.getElementById(i.toString()+c.toString()).innerHTML!="*")
                      conta++;
             }
          }
          if (conta==16)
              alert("Hai vinto!");
          return;
      }
   //Caso 3: ci sono già due caselle scoperte e sono entrambe diverse.
   if (caselle_aperte.length==2){
      //Nascondi i simboli
      nascondiSimbolo(caselle_aperte[0]);
      nascondiSimbolo(caselle_aperte[1]);
      //svuota caselle_aperte
      caselle_aperte.pop();
      caselle_aperte.pop();
      return;
   }
}
/*
* Mostra semplicemente il simbolo in riga,colonna
*/
function mostraSimbolo(riga,colonna){
   document.getElementById(riga.toString()+colonna.toString()).innerHTML=array[((riga-1)*4)+colonna-1];
}
/*
*Nasconde semplicemente il simbolo in riga, colonna
*/
function nascondiSimbolo(riga,colonna){
   document.getElementById(riga.toString()+colonna.toString()).innerHTML="*";
}
/*
*Come sopra, ma overload per un solo parametro di tipo stringa(es:"32, riga 3, colonna 2)
*/
function nascondiSimbolo(_stringa){
   stringa = new String(_stringa);
   riga = stringa.charAt(0);
   colonna = stringa.charAt(1);
   document.getElementById(riga.toString()+colonna.toString()).innerHTML="*";
}
/*
*Ritorna il simbolo che c'è memorizzato in riga, colonna
*/
function getSimboloAt(riga,colonna){
  riga--;
  colonna--;
  return array[(riga*4)+colonna];
}
/*
*Come sopra, ma overload per un solo parametro di tipo stringa(es:"32, riga 3, colonna 2)
function getSimboloAt(_stringa){
  stringa = new String(_stringa);
  riga = stringa.charAt(0);
  colonna = stringa.charAt(1);
  riga--;
  colonna--;
  return array[(riga*4)+colonna];
}
/*
*Inizia un nuovo gioco
*/
function nuovoGioco(){
  //creo di nuovo un array
  array  = new Array();
  //idem
  caselle_aperte=new Array();
  //array che contiene i simbolo, per ora solo lettere
  var array_simboli= new Array("a","b","c","d","e","f","g","h");
  var simbolo;
  //Cancello il contenuto delle caselle e metto "*" al posto
  for (i=1;i<=4;i++)
     for(c=1;c<=4;c++) {
        riga = Math.floor(i/4)+1;
        colonna = i-((riga-1)*4);
        document.getElementById(i.toString()+c.toString()).innerHTML="*";
        document.getElementById(i.toString()+c.toString()).setAttribute("style","background-color:white;");
     }
   //Preparo l'array a random
  var pos1,pos2;
  for (i=0;i<8;i++){
   while(array[pos1]!=null)
       pos1=Math.floor((Math.random()*16));
   array[pos1]=array_simboli[i];
   while(array[pos2]!=null)
       pos2=Math.floor((Math.random()*16));
   array[pos2]=array_simboli[i];
   }
   //Correggo bug, secondo il quale "a" viene memorizzata una sola volta mentre l'altra è null
   for (i=0;i<16;i++)
     if (array[i]==null){
         array[i]="a";
         break;
     }
}
E da qui potrete vedere il risultato:

sabato 9 novembre 2013

Tutorial GTK+ 3.0 . Parte 3 - Packing

Durante lo sviluppo di una applicazione, potreste avere bisogno di aggiungere più oggetti in una finestra. L'esempio precedente ha usato solamente un widget e abbiamo utilizzato semplicemente una chiamata alla funzione  gtk_container_add() per "impacchettare" il widget dentro la finestra.Ma quando avete bisogno di aggiungere più oggetti in una finestra, diviene importante controllare in che modo ogni widget deve essere posizionato e dimensionato. Ecco a cosa serve l'impacchettamento. GTK+ contiene una vasta collezione di containers la cui funzione è quella di controllare il layout dei widget figli aggiunti alla finestra. Vedi Layout Containers per una visione generale. L'esempio seguente mostra come il container GtkGrid vi permette di sistemare i tasti: ecco a voi il famoso Hello World in GTK+:
#include <gtk/gtk.h>

static void print_hello (GtkWidget *widget, gpointer   data){
  g_print ("Hello World\n");
}

int main (int   argc, char *argv[]){
  GtkWidget *window;
  GtkWidget *grid;
  GtkWidget *button;

  /* Questa è una funzione di callback. Gli argomenti da riga di comando
 * vengono ignorati in questo esempio. Se ne parlerà più avanti di queste funzioni.
 */
  gtk_init (&argc, &argv);

  /* crea una nuova finestra e imposta il titolo */
  window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
  gtk_window_set_title (GTK_WINDOW (window), "Grid");
  g_signal_connect (window, "destroy", G_CALLBACK (gtk_main_quit), NULL);
  gtk_container_set_border_width (GTK_CONTAINER (window), 10);

  /* Qui creiamo il container per impacchettare i vari widget */
  grid = gtk_grid_new ();

  /* aggiunge il container alla finestra */
  gtk_container_add (GTK_CONTAINER (window), grid);

  button = gtk_button_new_with_label ("Button 1");
  g_signal_connect (button, "clicked", G_CALLBACK (print_hello), NULL);

  /* Posiziona il primo tasto nella cella (0, 0), e gli fa occupare
   * solo 1 cella orizzontalmente e verticalmente
   */
  gtk_grid_attach (GTK_GRID (grid), button, 0, 0, 1, 1);

  button = gtk_button_new_with_label ("Button 2");
  g_signal_connect (button, "clicked", G_CALLBACK (print_hello), NULL);

  /* Posiziona il secondo tasto nella cella (1, 0), e gli fa occupare
   * solo una cella orizzontalmente and verticalmente
   */
  gtk_grid_attach (GTK_GRID (grid), button, 1, 0, 1, 1);

  button = gtk_button_new_with_label ("Quit");
  g_signal_connect (button, "clicked", G_CALLBACK (gtk_main_quit), NULL);

  /* Posiziona il tasto Quit(Esci) nella cella (0, 1), e gli fa riempire
   * 2 colonne.
   */
  gtk_grid_attach (GTK_GRID (grid), button, 0, 1, 2, 1);

  /* Ora che abbiamo impacchettato i widgets, li mostriamo tutti 
   * in una volta, chimaando gtk_widget_show_all() sulla finestra.
   * This call recursively calls gtk_widget_show() on all widgets
   * that are contained in the window, directly or indirectly.
   */
  gtk_widget_show_all (window);

  /* Tutte le applicazioni GTK+ devono avere un gtk_main(). Il controllo finisce qui
   * e rimane in attesa di un qualche evento (come la pressione di un tasto
   * o un evento del mouse) fintantoché gtk_main_quit() viene invocato.
   */
  gtk_main ();

  return 0;
}