Exemplo simples do uso de GtkBuilder

Ok… existe um jeito de construir uma janela e atribuir os sinais aos widgets sem escrever muito código usando o Glade. O exemplo abaixo é bem simples: Apenas uma janelinha com um label e um button:

Essa já é a janela da aplicação rodando!

Para fazer isso vamos ao Glade criar uma GtkWindow e, dentro dela, colocar uma GtkBox com orientação vertical (o padrão) e duas divisões:

Note que mudei o nome do objeto da janela para window, ao invés do default window1. Modifiquei também a propriedade title na aba “General” para “Hello”. O motivo de colocarmos a GtkBox acima é que queremos colocar uma janela filha embaixo da outra. Daí, colocamos elas na GtkBox e temos algo assim:

Depois de modificadas as características visuais de cada objeto vou às abas “Signals” tanto da window quanto de button1 e colocarei o nome da função gtk_main_quit nos eventos destroy e clicked:

Tudo o que temos que fazer agora é salvar o projeto no formato GtkBuilder e fazer um programinha para carregá-lo:

#include <gtk/gtk.h>

int main(int argc, char *argv[])
{
  GtkBuilder *builder;
  GtkWidget *window;

  gtk_init(&argc, &argv);

  builder = gtk_builder_new();

  // "App.glade" é o arquivo que salvamos antes!
  // Existem outros lugares de onde podemos obter o XML!
  gtk_builder_add_from_file(builder, "App.glade", NULL);

  // Conecta os eventos assinalados no XML com as 
  // respectivas funções.
  gtk_builder_connect_signals(builder, NULL);

  // Pega o objeto window e mostra-o.
  window = GTK_WIDGET(gtk_builder_get_object(builder,
                      "window"));
  gtk_widget_show_all(window);

  gtk_main();

  return 0;
}

Voilà! Basta compilar e executar:

$ gcc -O3 -s -o hello hello.c \
`pkg-config --cflags --libs gtk+-3.0`

Repare que isso ai exige o GTK+3.x.

UPDATE:

Duas pequenas modificações sem muita importância… É conveniente que o evento “clicked” do botão requisite que a janela seja fechada. Isso pode ser feito modificando o evento para chamar gtk_window_close com o parâmetro window (aparecerá uma lista com os objetos contidos na janela ao clicar em “User Data”).

A outra modificação é quanto às própria janela window, o label label1 e o botão button1. Em window, habilite os tamanhos default e ajuste-os em 320×200 (por exemplo). No label1, na aba “Packing”, habilite “Expand”. E em button1, mude o “Button Content” para “Stock Button” e selecione “gtk-close” na combobox. A diferença na janela final será essa:

No caso do botão, se seu sistema estiver em português, aparecerá “Fechar” e se os botões com ícones estiverem habilitados, aparecerá um x vermelho, ao lado… :)

Programação Gráfica em C: Gnome Tool Kit (GTK+)

Se você gostou da pequena série de textos sobre programação gráfica usando a Win32 API (aqui, aqui e aqui), saiba que apenas arranhei o assunto, mas toda a base está lá… Até mesmo para ambientes diferentes como X11, no Linux e Unixes em geral. É claro que a coisa toda é um pouco mais simples e diferente nesses outros ambientes.

No caso do Linux (e Unixes) o ambiente gráfico não é integrado ao sistema operacional. Trata-se de um software “servidor” ou daemon. A aplicação literalmente envia comandos para o daemon pedindo para desenhar uma janela, movê-la, mudar de tamanho, pintar uma área e o daemon manda mensagens de volta… Em essência, é o que o Windows também faz. Mas, no X11, as janelas são desenhadas por um desktop environment manager, que é um módulo que roda sobre o daemon X11. Existem vários: Gnome, Unity, Wayland, Cinnamon, MATE, KDE, XFCE, WindowMaker, Motif etc.

Os dois desktop managers mais famosos e mais aceitos são Gnome e KDE e, por isso, as duas bibliotecas mais usadas para desenvolvimento “em janelas” para Linux são o GTK+ e o Qt. O primeiro é uma conjunto de bibliotecas para serem usadas em C. A outra, é uma class library, um conjunto de bibliotecas para serem usadas em C++. Neste aspecto, o GTK+ é mais parecido com a Win32 API do que o Qt. Eis um “hello, world” com o GTK+:

#include <stdlib.h>
#include <gtk/gtk.h>

int main(int argc, char *argv[])
{
  GtkWidget *mainWindow;
  GtkWidget *label;

  gtk_init(&argc, &argv);

  // Cria a janela, ajusta o tamanho, o título e
  // conecta o evento "destroy" à função gtk_main_quit.
  mainWindow = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_default_size(GTK_WINDOW(mainWindow),
                              320, 200);
  gtk_window_set_title(GTK_WINDOW(mainWindow), 
                       "Hello App");
  gtk_signal_connect(GTK_OBJECT(mainWindow),
                     "destroy",
                     GTK_SIGNAL_FUNC(gtk_main_quit),
                     NULL);

  // Cria o label e ajusta o texto.
  label = gtk_label_new(NULL);
  gtk_label_set_text(GTK_LABEL(label), "Hello, world!");

  // Adiciona o label à janela.
  // Mostra a janela e seus filhos.
  gtk_container_add(GTK_CONTAINER(mainWindow), label);
  gtk_widget_show_all(mainWindow);

  // Loop principal.
  gtk_main();

  return EXIT_SUCCESS;
}

Para compilar é sempre bom usar um utilitário que nos dará a lista de diretórios onde acharmos os headers e as libraries:

$ pkg-config --cflags gtk+-2.0
-pthread -I/usr/include/gtk-2.0 -I/usr/lib/x86_64-linux-gnu/gtk-2.0/include \
-I/usr/include/gio-unix-2.0/ -I/usr/include/cairo -I/usr/include/pango-1.0 \
-I/usr/include/atk-1.0 -I/usr/include/cairo -I/usr/include/pixman-1 \
-I/usr/include/libpng12 -I/usr/include/gdk-pixbuf-2.0 -I/usr/include/libpng12 \
-I/usr/include/pango-1.0 -I/usr/include/harfbuzz -I/usr/include/pango-1.0 \
-I/usr/include/glib-2.0 -I/usr/lib/x86_64-linux-gnu/glib-2.0/include \
-I/usr/include/freetype2

$ pkg-config --libs gtk+-2.0
-lgtk-x11-2.0 -lgdk-x11-2.0 -lpangocairo-1.0 -latk-1.0 -lcairo \
-lgdk_pixbuf-2.0 -lgio-2.0 -lpangoft2-1.0 -lpango-1.0 -lgobject-2.0 -lglib-2.0 \
-lfontconfig -lfreetype

Impressionante, não? GTK+ exige um porrilhão (como sempre, essa é uma especificação de grandeza! hehe) de diretórios contendo arquivos header e um punhado de bibliotecas! Para compilarmos o nosso programinha podemos misturar as opções --cflags e --libs em uma linha só, assim:

$ gcc -O3 -s -o hello hello.c `pkg-config --cflags --libs gtk+-2.0`

Estou usando as opções -O3 e -s para otimizações e eliminar símbolos não necessários no arquivo binário final. O resultado da execução de hello é esse:

Agora, mova a janela, mude o tamanho, minimize, maximize… tá tudo lá num pequeno executável de 10 KiB!

O que pode surpreender é o aparente uso de Orientação a Objetos em um código em C. Os macros GTK_WIDGET, GTK_WINDOW e GTK_LABEL são apenas type castings para que a função não reclame que estamos passando o tipo errado e, ao mesmo tempo, todo objeto visível é um widget (GtkWidget).

Para tornar as coisas mais rápidas, a identificação de cada objeto é, de fato, um ponteiro que aponta para ele próprio. Nada de “handles”, como no Win32. E, embora pudéssemos criar nosso próprio loop de mensagens, GTK+ fornece a função gtk_main() que faz isso por nós. Ele permite registrar callbacks para hooks no loop, como exemplo, quando o loop estiver esperando por mensagens, pode executar uma função idle

Ahhh… quanto ao pkg-config, use a opção --list-all para ver todas as bibliotecas que ele consegue obter as listas de headers e libs

Por que gtk_init() toma ponteiros para argc e argv?

A função gtk_init() pode receber opções da linha de comando diretamente para ele… No caso do X11, por exemplo, a opção --display indica o daemon onde as janelas serão criadas (pode estar em outra máquina, por exemplo!)… A função retira da lista de parâmetros aqueles que são destinados apenas ao GTK+!

Um aviso sobre o sistema de coordenadas usado no GTK+:

Se você já mexeu com Applets em JAVA, deve se lembrar da antiga biblioteca AWT (Abstract Widget Toolkit). Ele foi “copiado” de bibliotecas como XLib e GTK+… Os containers do GTK+ (GtkWindow, por exemplo) usam o sistema de coordenadas “geográfico” por default, onde, ao inserir um único widget-filho ele é colocado no “centro”, ocupando a área útil toda… mas, podemos colocá-lo ao norte, ao sul, ao leste ou oeste, assim como era feito no AWT.

Isso não significa que não podemos usar um sistema de coordenadas cartesiano, mas, para isso, precisaríamos incluir o container GtkFixed na nossa janela e esse container é o que permite posicionamento cartesiano… A maioria das aplicações feitas com GTK+ não fazem isso…

Existe um utilitário para desenhar janelas?

Sim… existe o Glade:

Screenshot do Glade

Ele gera um arquivo XML contendo a descrição de sua janela e o GTK+ possui funções que interpretam esse XML e montam a janela completa (GtkBuilder). É a perfeita implementação do MVC. O View é apenas o arquivo XML contendo a parte visual e o código que lida com ele é o seu Model e Controller. Num outro artigo explico como usá-lo…

Gtk+ é portável!

Um detalhe final é que GTK+ é portável. Existe o GTK+ para Linux, OS/X, FreeBSD, mas também tem para Windows! Isso quer dizer que você pode criar programas gráficos para todas as plataformas com o mesmo conjunto de bibliotecas. No caso do Windows, terá que usar o MinGW para compilar com o GCC e carregar junto um caminhão de DLLs que colocará em C:\Windows\System32 (aqui é mostrado como)… Note que é perfeitamente possível compilar sua aplicação com GTK+ usando o Visual Studio.

Note que, no Windows, não é necessário usar aquela construção biruta de WinMain(). O bom e velho main funciona!

GTK+ 2.0 e GTK+ 3.0

Existem duas implementações da biblioteca porque existem, hoje, dois “Gnomes” (versão 2.x e 3.x) diferentes… O ‘G’ do GTK vem de Gnome e é para desenvolver aplicações para ele que a biblioteca existe. Clique nos links para obter documentação de referência a ambas as versões: GTK+ 2.0 e GTK+ 3.0. Note, também, que a GLib é parte integrante e, portanto, uma pancada de algoritmos está à sua disposição (listas, árvores etc)…

Como funciona o ext2fs?

No, agora exinto, projeto Heisenberg OS, tinha deixado para decidir qual file system usar e, ao mesmo tempo, iniciado os estudos a respeito do ext2fs, usado no Linux e outros unixes por ai. Um filesystem é uma estrutura de dados que permite usarmos a abstração de “arquivos” para armazenamento de blocos de dados em dispositivos como HDs, disquetes, pen-drives etc. Ao invés de pedirmos ao sistema “leia 16 setores a partir da trilha 612, cabeça 2, setor 10”, podemos simplesmente pedir “leia 8 kB do arquivo ‘xpto.txt’, do diretório /var/tmp/”.

Como isso funciona? É típico que os dispositivos de armazenamento de blocos (discos) suportem um tamanho de bloco fixo de 512 bytes — que equivale a um único setor — mas, o ext2fs e seus descendentes usam tamanhos de blocos múltiplos de 1 kB (1, 2, 4 ou 8 kB, para ser mais exato). Um tamanho de bloco típico é 4 kB (existe um motivo para isso, explico depois). Assim, este é o tamanho mínimo de dados que podem ser lidos/gravados em disco.

blocks

Além dos blocos, ext2fs usa grupos de blocos… Com blocos de 4 kB temos grupos de 8192 blocos estruturados dessa maneira:

group_blocks4

Um superbloco descreve todo o filesystem. Ele não descreve o grupo de blocos, mas todo o seu disco. Além disso, um superbloco tem sempre o tamanho exato de 1 kB, mesmo que o bloco tenha 4 kB (neste caso os 3 kB restantes ficam inúteis).

O bloco dos descritores de grupo descrevem o grupo corrente e tem uma estrutura bem simples:

struct ext2_group_desc
{
  /* bloco do "bitmap de blocos". */
  unsigned int   bg_block_bitmap;

  /* bloco do "bitmap de i-ndes". */
  unsigned int   bg_inode_bitmap;

  /* bloco inicial da tabela de inodes. */
  unsigned int   bg_inode_table;

  /* Quantidade de blocos livres. */
  unsigned short bg_free_blocks_count; 

  /* Quantidade de inodes livres. */
  unsigned short bg_free_inodes_count;
 
  /* Quantidade de diretórios. */
  unsigned short bg_used_dirs_count;   

  unsigned short bg_reserved0;
  unsigned int   bg_reserved1;
};

Os dois blocos de bitmap, descrevem quais blocos estão ocupados ou livres (bitmap de blocos) e quais entradas da tabela de inodes estão ocupadas ou livres (bitmap de i-nodes). Seguem os n blocos de inodes e, por fim, os blocos que contém dados dos arquivos. Esses dois blocos são conjuntos de bits, onde cada bit indica um bloco ou item da tabela de inodes.

Um inode é um descritor de um arquivo… Ele diz qual é o tipo do arquivo, o tamanho, permissões, etc. Essa estrutura não contém o nome do arquivo, que estará localizado num arquivo especial de diretório… O número do inode é a identificação real de um arquivo, não o seu nome! Na figura acima temos os blocos de inodes e de dados contendo número variável de blocos. Todos as outras estruturas ocupam exatamente 1 bloco…

Determinando o tamanho dos blocos de inodes e dados:

Lembre-se: Cada bloco tem 4 kB de tamanho… Isso significa que no bitmap de blocos temos 4096 vezes 8 bits e cada bit corresponde a um bloco em uso ou livre no grupo. Assim, temos 32768 bits e, consequentemente, 32768 blocos no grupo.

Eis duas rotinas de exemplo de como determinar o primeiro bloco livre e determinar quantos blocos adjacentes estão livres:

#include <stdio.h>
#include <string.h>
#include <limits.h>

/* Tamanho de 1 bloco. */
#define BLOCK_MAP_SIZE  4096

/* Vou usar um "unsigned long" para acelerar as rotinas. */
#define BITMASK_SIZE    (sizeof(unsigned long)*8)

/* Faça de conta que um bitmap de blocos esteja carregado 
   aqui. */
extern unsigned char block_map[];

/* A partir de um bloco livre, conta quantos blocos 
   contiguos também estão livres. */
int count_contiguos_free_blocks(int blocknum)
{
  unsigned long *bptr;

  /* Usa bptr_end como "batente". */
  unsigned long *bptr_end = 
    (unsigned long *)(block_map + BLOCK_MAP_SIZE);

  int local_block_num;
  int count = 0;

  if (blocknum < BLOCK_MAP_SIZE)
  {
    bptr = (unsigned long *)block_map + 
             (blocknum / BITMASK_SIZE);

    local_block_num = blocknum % BITMASK_SIZE;

    while (bptr < bptr_end)
    {
      if (*bptr & (1UL << local_block_num)) 
        break; 

      count++; 

      if (++local_block_num >= BITMASK_SIZE)
      {
        local_block_num = 0;
        bptr++;
      }
    }
  }

  return count;
}

/* Acha o primeiro bloco livre no bitmap */
int get_first_free_block_index(void)
{
  unsigned long *bptr = (unsigned long *)block_map;

  /* Usa bptr_end como "batente". */
  unsigned long *bptr_end =
    (unsigned long *)(block_map + BLOCK_MAP_SIZE);

  int free_block = -1;
  int block_bit = 0;
  int bit_count = 0;

  while (bptr < bptr_end)
  {
    if ((*bptr & (1UL << bit_count)) == 0) 
      return block_bit; 

    block_bit++; 

    if (++bit_count >= BITMASK_SIZE)
    {
      bptr++;
      bit_count = 0;
    } 
  }

  return free_block;
}

A figura anterior nos diz que o bloco contendo o superbloco pode não existir “dependendo do grupo”. É que, ao invés de manter um único superbloco em todo o filesystem ou cópias em todos os grupos, para poupar espaço, a revisão 1 do ext2fs resolveu colocar cópias do superbloco apenas nos grupos 0, 1, 3n, 5n e 7n, onde n é um inteiro e n \geqslant 1. Isso permite manter cópias do superbloco para que, em caso de catástrofe, ele possa ser recuperado a partir de uma das (poucas) cópias espalhadas pelo disco.

Existe um detalhe importante para quem quiser implementar ext2fs por sua própria conta: O primeiro superbloco (no grupo 0) está sempre localizado na posição 1024 do bloco 0, do grupo 0 — o primeiro quilobyte é reservado para um bootloader (se existir um)! Nas demais cópias, o superbloco localiza-se no início do do bloco 0 do grupo (embora eu não tenha certeza absoluta desse fato!).

Para ilustrar a distribuição das cópias do superbloco, eis um mapa dos primeiros 1024 grupos de um disco de 500 GB. Os caracteres ‘S’ marcam o grupo que contém uma cópia do superbloco, os caracteres ‘.’ indicam grupos que não as possui:

Block group map (começa no grupo 0):
===================================
SS.S.S.S.S...............S.S.....................S..............
.................S...........................................S..
................................................................
...................................................S............
................................................................
.......................S........................................
................................................................
................................................................
................................................................
.................................................S..............
................................................................
.........................S......................................
................................................................
................................................................
................................................................
................................................................

Note que os blocos 0, 1, 3, 5, 7 ,9, 25, 27, 49, 81 etc contém as cópias… Com os superblocos dispersos desse jeito, sobra mais espaço para dados e temos um grande número de backups, se necessário!

Mas, o que dermina mesmo o tamanho do grupo é o bloco de bitmap de blocos. Neste bloco, cada bit, dos 32768 (4096 \cdot 8) indica a ocupação de cada um dos blocos do grupo. Neste bitmap teremos 1027 ou 1028 blocos “reservados” para:

  • Superbloco: 1 bloco (se existir a cópia);
  • Bloco descritor do grupo: 1 bloco;
  • Bitmap de blocos do grupo: 1 bloco;
  • Bitmap de inodes do grupo: 1 blocos;
  • inodes: 1024 blocos.

Porque 1024 blocos para os inodes? Pelo mesmo motivo que temos 8192 blocos num grupo. O Bitmap de inodes nos permite mander o estado de 32768 inodes e cada inode tem 128 bytes de tamanho. Portanto, 1 bloco contém 32 inodes. Logo, precisamos de 1024 blocos para conter todos os inodes. 7164 (se houver um superbloco) ou 7165 blocos para uso dos dados.

inodes:

Os inodes, dentro de um bloco são numerados de 0 até 8191, mas no contexto do filesystem eles são numerados de 0 até numero\_de\_grupos \cdot 4096. Isso permite que um inode num grupo faça referência a um inode em outro grupo e isso é útil para casos onde não há alternativa senão fragmentar um arquivo entre blocos…

A estrutura básica de um inode é:

struct ext2_inode {
  unsigned short i_mode;   /* "modo" como em 'chmod'. */
  unsigned short i_uid;    /* UserID do arquivo. */
  unsigned int   i_size;   /* Tamanho, em bytes. */
  unsigned int   i_atime;  /* timestamp do último acesso. */
  unsigned int   i_ctime;  /* timestamp da criação. */
  unsigned int   i_mtime;  /* timestamp da modificação. */
  unsigned int   i_dtime;  /* timestamp da "deleção". */
  unsigned short i_gid;    /* GroupID do arquivo. */
  unsigned short i_links_count;  /* Quantidade de HARD-links. */
  unsigned int   i_blocks; /* Quantidade de blocos. */
  unsigned int   i_flags;  /* Diversos flags. */
  unsigned int   i_reserved1;
  unsigned int   i_block[15];  /* "Ponteiros" para os blocos. */

  unsigned int   i_generation; /* Versão do arquivo (para NFS) */

  /* Informações sobre ACLs (selinux?). */
  unsigned int   i_file_acl;
  unsigned int   i_dir_acl;

  /* Informações sobre "fragmentos". */
  unsigned int   i_faddr;
  struct {
      unsigned char  l_i_frag;
      unsigned char  l_i_fsize;
      unsigned short i_pad1;
      unsigned short l_i_uid_high; /* Parte superio do UserID. */
      unsigned short l_i_gid_high; /* Parte superior do GroupID. */
      unsigned int   l_i_reserved2;
  } linux;
};

Existem muitos detalhes escondidos nessa estrutura. O que nos interessa são os campos i_blocks, que nos dá a quantidade de blocos usados pelo arquivo; e o array i_block[], que nos diz os blocos usados pelo arquivo… Isso pode parecer estranho, já que temos apenas 15 entradas… As 12 primeiras contém o número exato de um bloco de dados; a 13ª entrada contém o número de um bloco que contém um array com números de blocos de dados; a 14ª entrada faz a mesma coisa, mas é uma indireção dupla (contém um número do bloco que contém um array onde cada item é um nº de bloco que aponta para outro array que contém os números de blocos de dados)… A 15ª entrada faz o mesmo, mas com indireção tripla!!! A figura abaixo ilustra.
ext2_inode
Com esse esquema dá para alocar um espaço absurdamente grande para um único arquivo e usar uma única entrada de inode de 128 bytes!!! Se cada bloco tem 4 kB e cada número de bloco tem 32 bits de tamanho, então cada bloco comporta 1024 números de blocos de dados… Na indireção simples 1024 blocos podem ser indicados. Na indireção dupla, 1048576 (1024 tabelas com 1024 entradas). Na tripla: 1073741824. Somando isso tudo aos 12 blocos que já constam no inode temos a possibilidade de usar 1074791436 blocos de 4 kB para um único arquivo, ou seja, cerca de 4 TB!

Repare que, nos inodes, o que vai no array i_block é o número de um bloco (a mesma coisa nas tabelas de blocos, se usadas as indireções), portanto, o conteúdo de um arquivo pode cruzar a fronteira de um único grupo de blocos.

Diretórios:

Um diretório é um arquivo que contém uma lista da seguinte estrutura:

struct ext_dir_entry_2
{
  unsigned int   inode;     /* inode do "aquivo". */
  unsigned short rec_len;   /* Tamanho dessa entrada. */
  unsigned char  name_len;  /* Tamanho do nome do arquivo. */
  unsigned char  file_type; /* "tipo" do arquivo. */
  char           name[];    /* Nome, de até 255 caracteres. */
}

Onde o “tipo” é uma constante que diz se o “arquivo” é: desconhecido, regular, diretório, dispositivo do tipo caracter, dispositivo do tipo block, fifo, socket ou link simbólico. Repare que a única relação entre o nome e os dados do arquivo estão no primeiro campo: O número do inode associado a ele.

Obtendo o MAC Address da interface de rede…

MAC é a sigla de Media Access Control. O MAC Address é o endereço da sua placa de rede. Este endereço é diferente do IP Address. Este último é o endereço da Inetnet. Se você pretende lidar com a camada de link (Link Layer ou Layer 2), você precisa saber qual é o endereço MAC da sua placa de rede e, se quiser saber o endereço MAC de algum dispositivo remoto, dado um endereço IP, deverá usar um protocolo conhecido como ARP (Address Resolution Protocol).

Não estou interessado, aqui, por enquanto, em obter o endereço MAC de outros dispositivos a não ser o da sua placa de rede.

O método é bem simples: Crie um socket com base na família desejada (AF_INET ou AF_INET6) e peça ao driver de rede para te dar o MAC Address para o dispositivo que você quer, usando o socket previamente criado:

/* Teste: Pega MAC address de eth0. */
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <net/if.h>

/* Um endereço MAC tem 6 bytes de tamanho! */
typedef union {
  unsigned char u8[6];
  unsigned long long u64; 
} mac_addr_t;

/* Definição do macro macaddr_ntohll(), se precisarmos dele! */
#if __BYTE_ORDER == __LITTLE_ENDIAN
  #ifdef __x86_64
    #define macaddr_ntohll(x) (__builtin_bswap64((x)) >> 16)
  #else
    // IA-32 não tem __builtin_bswap64()!
    #define macaddr_htonll(x) { \
      unsigned long long _a = (unsigned long long)(x); \
      _a >>= 16; \
      unsigned int *_b = (unsigned int *)&_a, tmp; \
      tmp = *_b; *_b = *(_b + 1); *(_b + 1) = tmp; \
      *((unsigned long long *)&(x)) = \
        __builtin_bswap32(*_b) + ((unsigned long long)__builtin_bswap32(*(_b + 1)) << 32); \
    }
  #endif
#else
  #define macaddr_ntohll(x) ((x) & 0x0000ffffffffffffULL)
#endif

int main(void)
{
  int fd;
  struct ifreq ifr = {};
  mac_addr_t *macptr;

  /* Cria o socket.  */
  if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
  {
    perror("socket");
    return 1;
  }

  /* Preenche a estrtutura que o driver precisa. */
  ifr.ifr_addr.sa_family = AF_INET;
  strncpy(ifr.ifr_name, "eth0", IFNAMSIZ - 1);

  /* Chama o driver pedindo o MAC Address */
  if (ioctl(fd, SIOCGIFHWADDR, &ifr) == -1)
  {
    perror("ioctl");
    close(fd);
    return 1;
  }

  macptr = (mac_addr_t *)ifr.ifr_hwaddr.sa_data;

  printf("%02x:%02x:%02x:%02x:%02x:%02x\n",
    macptr->u8[0], macptr->u8[1], macptr->u8[2], 
    macptr->u8[3], macptr->u8[4], macptr->u8[5]);

  /* Imprime em hexadecimal, note que o endereço está 
     orientado como BIG ENDIAN! */
  printf("0x%llx\n", macaddr_ntohll(mac->u64));

  close(fd);

  return 0;
}

Isso, provavelmente, só funcionará para LINUX. Ainda é preciso verificar se funciona no FreeBSD e OS/X.
No Windows, nem pensar!

Obtendo o modelo e número de série de um HD… No LINUX…

Um amigo está tentando obter informações sobre como obter dados de hardware na tentativa de elaborar um esquema de “proteção contra cópia”. Eu acho isso uma grande besteira, mas aqui vai uma dica para obter o número de série e o modelo de um HD, no Linux:

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/hdreg.h>

int main(int argc, char *argv[])
{
  struct hd_driveid hd;
  int fd;

  /* Apenas root pode fazer isso! */
  if (geteuid() != 0) 
  {
    fprintf(stderrr, 
      "ERRO: Privilégio de root necessário!\n");
    return 1;
  }

  /* OBS: Se o parâmetro não for passado ele 
          será NULL, causando erro de open(). */

  if ((fd = open(argv[1], O_RDONLY|O_NONBLOCK)) == -1) 
  {
    fprintf(stderr, 
      "ERRO: Não posso abrir disposivivo %s\n", argv[1]);
    return 1;
  }

  if (!ioctl(fd, HDIO_GET_IDENTITY, &hd))
  { 
    printf("Modelo do HD    : %s\n"
           " Número de Série: %s\n", 
           hd.model, hd.serial_no);

    close(fd);
    return 0;
  }

  close(fd);

  perror("ERRO: HDIO_GET_IDENTITY");
  return 1;
}

Convertendo vídeos pelo Nautilus, mas tem alguns problemas…

Eis que eu me aventuro a criar scripts bash para o Nautilus (“file manager” do Linux). O primeiro teste, é claro, é meu script de conversão de vídeos. Quais são os requisitos? Dado um ou mais arquivos selecionados, preciso obter o número total de frames de cada um e convertê-los ao mesmo tempo que mostro o progresso da conversão. Isso parece ser um trabalho para o ‘ffmpeg’ e para o ‘zenity’.

O problema do frame count:

Começo bem: O ‘ffmpeg’ não me dá, facilmente, a quantidade total de frames (frame count) de um vídeo. Vi outros scripts que pegam essa informação através de um utilitário chamado ‘tcprobe’ (do pacote ‘transcode’), mas ele falha miseravelmente com arquivos ‘.mpg’ ou ‘.mpeg’. Existe ainda um outro problema para esses tipos de arquivo, que mostrarei daqui à pouco. Outro utilitário que pode ser usado é o ‘mediainfo’ (do pacote do mesmo nome):

$ mediainfo myvideo.avi
General
Complete name                            : myvideo.avi
Format                                   : AVI
Format/Info                              : Audio Video Interleave
File size                                : 13.5 MiB
Duration                                 : 2mn 41s
Overall bit rate                         : 701 Kbps
Writing application                      : Lavf53.32.100

Video
ID                                       : 0
Format                                   : MPEG-4 Visual
Format profile                           : Simple@L1
Format settings, BVOP                    : No
Format settings, QPel                    : No
Format settings, GMC                     : No warppoints
Format settings, Matrix                  : Default (H.263)
Codec ID                                 : FMP4
Duration                                 : 2mn 41s
Bit rate                                 : 561 Kbps
Width                                    : 1 280 pixels
Height                                   : 720 pixels
Display aspect ratio                     : 16:9
Frame rate                               : 24.000 fps
Color space                              : YUV
Chroma subsampling                       : 4:2:0
Bit depth                                : 8 bits
Scan type                                : Progressive
Compression mode                         : Lossy
Bits/(Pixel*Frame)                       : 0.025
Stream size                              : 10.8 MiB (80%)
Writing library                          : Lavc53.61.100

Audio
ID                                       : 1
Format                                   : MPEG Audio
Format version                           : Version 1
Format profile                           : Layer 3
Mode                                     : Joint stereo
Mode extension                           : MS Stereo
Codec ID                                 : 55
Codec ID/Hint                            : MP3
Duration                                 : 2mn 41s
Bit rate mode                            : Constant
Bit rate                                 : 128 Kbps
Channel(s)                               : 2 channels
Sampling rate                            : 44.1 KHz
Compression mode                         : Lossy
Stream size                              : 2.47 MiB (18%)
Alignment                                : Aligned on interleaves
Interleave, duration                     : 26 ms (0.63 video frame)
Writing library                          : LAME3.99.3

Onde é que está o frame count? Você pode pensar que se pegarmos o “Frame rate” e multiplicarmos pelo “Duration” teremos a resposta. Acontece que “Frame Rate” nem sempre é exato (ou, melhor, inteiro!). O ‘frame rate’ pode ter uma parte  fracionária (por exemplo: 23.98). Pode-se supor que isso nos daria uma quantidade menor de frames do que o arquivo realmente tem, mas veremos que isso também pode estar errado…

Felizmente o ‘mediainfo’ possui um macete:

$ mediainfo '--Inform=Video;%FrameCount%' myvideo.avi
3882

Note que se multiplicassmos o ‘Duration’ − (2*60+41), em segundos − com o ‘Frame Rate’ − 24 fps − obteríamos 3888 frames. Ou seja, 6 frames a mais do que o vídeo tem, de fato.

Usando zenity com ffmpeg

Agora que obtive o frame count, para o cálculo do percentual de progresso, basta ver como chamar o ‘ffmpeg’, passando o número do frame atual para o zenity.

O ‘ffmpeg’ me mostra a quanidade de frames já processado, atualizado na última linha:

frame= 1234 fps=659 q=31.0 Lsize=   13576kB time=00:01:32.70 bitrate= 687.7kbits/s

Tenho que arrumar um jeito de formatar essa informação e repassar para o zenity. Além disso: é preciso calcular a porcentagem do progresso com a fórmula:

percentual = (frame / frame_count) * 100

Depois disso é montar a string para ser apresentada no ‘zenity’ e passar também a porcentagem. É preciso obter o número depois de “frame=”. A ferramenta ideal para extrair essa informação, fazer os cálculos e preparar a saída é o ‘awk’ (poderia usar um script em perl ou python. Perl seria melhor, já que ele acompanha o Linux, por default):

FFMPEG_OPTIONS="-c:v libx264 -sameq -c:a ac3 -b:a 192k -ac 2 -map_metadata -1"

ffvideo () {
  # Pega o número total de frames do arquivo de entrada.
  local TOTAL_FRAMES=$(mediainfo '--Inform=Video;%FrameCount%' "$1")

  ffmpeg -y -i "$1" $FFMPEG_OPTIONS "$2" 2>&1 | \
      awk -vRS="\r" \
        '$1 ~ /^frame/ \
         { \
           gsub(/frame=/,""); \
           percent=int(($1 / '$TOTAL_FRAMES')*100); \
           print "#Converting '"'$1'"'.\\nTotal Frames: '$TOTAL_FRAMES'\\t\\tCurrent Frame:" $1 "\\n\\nPercent complete: " percent " %"; \
           print percent; \
           fflush(); \
         }' | \
      zenity --progress \
             --auto-close \
             --title="Converting..." \
             --width=500 \
             --percentage=0
}

O script do awk ficou um pouco confuso, não? É que estou misturando as variáveis dos campos do awk ($1) com as variáveis dos parâmetros da função ($1 e $2) e a variável local $TOTAL_FRAMES. Repare que a string com o script awk está entre aspas simples (‘), para que o bash não faça substituições escalares. Assim, o $1 do awk fica intacto… Quando quero usar o $1 do bash fecho a string usando aspas simples (‘), coloco o $1 (entre aspas duplas − ” − quando necesário) e reabro a string com (‘).

O script awk verifica se a linha (“record”, na nomenclatura do awk) começa com “frame”, retira a string “frame=” do caminho e coloca o valor do frame no primeiro “field” ($1). A partir daí é só fazer o cálculo e imprimir a mensagem formatada para o zenity. Atenção para o fato de que o zenity exige que as sequẽncias de escape (‘\t’ e ‘\n’) tenham duas barras (‘\\r’ e ‘\\n’). E o caracter ‘#’ parece ser o que diferencia a mensagem apresentada na janela do valor a ser usado na barra de progresso (por isso a porcentagem é impressa uma segunda vez).

A saída do awk é então repassada para o zenity.

Os problemas

Lembra-se que falei que arquivos ‘.mpg’ ou ‘.mpeg’ podem causar mais um problema além do frame_count não ser mostrado pelo ‘tcprobe’? Este é um problema sério: Quando tenho que “juntar” dois ou mais vídeos, converto-os para ‘.mpg’ e depois uso o comando ‘cat’ para juntá-los. No exemplo abaixo tenho 3 vídeos chamados ‘video1.avi’, ‘video2.avi’ e ‘video3.avi’ e quero juntá-los num ‘myvideo.mpg’:

$ for i in {1..3}; do ffmpeg -i video$i.avi -sameq $i.mpg; done
$ cat 1.mpg 2.mpg 3.mpg > myvideo.mpg

O problema é que ‘mediainfo’ e o próprio ‘ffmpeg’ acham que o vídeo resultante têm o tamanho do primeiro vídeo (video1.mpg)… O ffmpeg consegue converter o video inteiro (myvideo.mpg) para qualquer outro formato, mas já que o mediainfo me dá o tamanho errado e o zenity vai tentar fechar quando chegar a 100%, você já viu onde isso vai dar, né?

Outros problemas: O script ‘awk’, acima, não é perfeito… Ele não checa por problemas.

Quanto ao zenity, é possível adicionar a opção ‘–auto-kill’, mas isso o fará enviar um sinal SIGKILL para o processo pai (o script bash). Por padrão, os sinais SIGKILL e SIGSTOP não podem ser tratados por quaisquer aplicações. Assim, mesmo que tenhamos pegado a quantidade total de frames com ‘mediainfo’, esse valor não é garantido que esteja correto. Se ele for menor que o valor real, o script morrerá antes da hora e o ffmpeg junto com ele, corrompendo o arquivo final.

Podemos ainda usar a opção –no-close, no zenity, mas isso não impede que o usuário feche a janela.

E quanto ao Nautilus?

Não há complicação alguma em fazer scripts para o Nautilus. Cada arquivo (ou diretório) selecionado é passado na linha de comando do script. Assim, para converter o vídeo basta fazer:

# Se não tem parâmetros, não tem arquivos selecionados!
if [ $# -lt 1 ]; then
  zenity --error --text="No files selected"
  exit
fi

# Enquanto existirem arquivos nos parâmetros...
while [ ! -z "$1" ]; do
  # Se for um arquivo...
  if [ -f "$1" ]; then
    # Converte.
    TMPFILE="$(mktemp $(dirname "$1")/tmp_XXXXXXXXX.mp4)"
    ffvideo "$1" "$TMPFILE"
    if [ $? -eq 0 ]; then
      rm "$1" 
      mv "$TMPFILE" "${1%.*}.mp4";)
    fi

    # Pega o próximo parâmetro via "shift".
    shift
  fi
done

Claro que podemos dar umas incrementadas: Verificar se o arquivo é um dos permitidos (.mp4, .avi, .asf, .wmv, .mpg, .mpeg, .m4v etc); Calcular, aproximadamente, o tamanho do arquivo (de forma empirica!), se for um .mpg ou .mpeg, e verificar se é maior do que deveria ser (pro caso da concatenação acima). Só que isso é uma gambiarra e pode não funcionar muito bem.

Você pode também tentar modificar o script awk para mostrar, além do nome do arquivo, dos frames e da porcentagem, o frame rate, o bit rate, o tamanho do arquivo e a marca de tempo [do arquivo] convertido.

Mas acho que você já pegou a idéia.

Instalando o script no Nautilus

Basta copiar o script para o diretório ‘~/.gnome2/nautilus-scripts/’ e voilà!

Gdb reloaded

Acho que encontrei um estímulo a mais para usar o gdb com frequência.

Outro dia comentei com o Fred (e enchi o saco dele com os meus screenshots) sobre o uso do emacs como front-end do gdb. É ótimo. Basta dar um M-x gdb e ele aparece bem do lado, se você tiver com duas views na tela. Caso não esteja, C-x 3, resolve.

Brinquei um pouco, achei interessante, mas aquela montoeira de etapas para se depurar me remeteram o meu velho assert ou mesmo usar puts e printf‘s bem colocados. Sim, meus programas são bem simples e isto me basta. Para que ficar carregando o gdb? Gdb, só quando a coisa fica muito feia.

Na realidade, escondo a minha preguiça em carregar os breakpoints e ao sair perdê-los… Sei que deve haver um jeito de fazer isto persistir entre sessões, mas sinceramente, estou usando o meu tempo para aprender `C’, mais e mais. (:-).

Hoje deparei com a dica sobre um truque interessante, visto na DDJ (sim a sempre querida DDJ!): Como colocar os breakpoints diretamente no seu código fonte!

Muito interessante e ainda de quebra achei utilidade prática, i.e. não somente para otimizar ou estilizar, para a técnica que o Fred sempre usa – asm inline.

O que o gdb precisa é de uma interrupção e ele para ali. Testei inicialmente o comando "embutido".

#define EMBED_BREAKPOINT asm volatile ("int3;")

E insere-se a macro no local de parada. Voilá ! Daí basta rodar o gdb e parar naquele ponto.

Bom… Mas nem tanto. Pois o gdb tem necessidades mais complexas. Afinal isto não é um breakpoint de fato, mas uma "parada brusca", aliás uma interrupção, na realidade uma TRAP, prevista no set de instruções do processador para efetuar estas paradas no código. Veja uma explicação melhor aqui.

Então, depois de indicado pela nossa leitura ao DDJ, cheguei neste blog, cujo nome é muito criativo: main is usually a function, o qual entendi que seja uma apologia declarada ao `C’. Lá é onde podemos ver a aplicação do recurso apresentado, só que em melhor estilo.

#define EMBED_BREAKPOINT                  \
    asm("0:"                              \
        ".pushsection embed-breakpoints;" \
        ".quad 0b;"                       \
        ".popsection;")

O que se faz neste hack é colocar labels nos pontos estratégicos para que o gdb os utilize ao rodar.

Vou poupá-los de códigos de demonstração, afinal eles estão no artigo original, no site do autor, os mesmo podem ser vistos e podem ser baixados via github.

Notas adicionais para que for compilar os exemplos apontados:

  • Instale o binutils-dev, o cabeçalho bfd.h – da library binary file descriptors – encontra-se lá.
  • Desnecessário dizer, mas não se esqueça de incluir -lbfd na linha de compilação.
  • Se for usar, como eu, o -std=c89, as declarações no interior dos `for’ devem ser retrabalhadas.
  • Há uma warning sobre redefinição do snprintf que não impede o funcionamento, entretanto, se usar -Werror, deve-se “pragmatizar”.
  • Se for usar o wrapper, melhor torná-lo acessível via /etc/alternatives (Debian, no meu caso):
  • # update-alternatives --install /usr/bin/dbg dbg /usr/local/bin/gdb-with-breakpoints 50
    
  • E, no caso do emacs, melhor customizar o GUD (Grand Unified Debugger).

No mais, o gdb-with-breakpoints funcionou bem aqui.

Continuar lendo