Alterando software livre do jeito Debian – Patch para o Transmission
Um dos argumentos dos evangelistas de software livre é que, tendo acesso ao código fonte do programa, você altera o que quiser. Mas quem tem o mínimo de experiência em desenvolvimento sabe que o processo é um pouquinho trabalhoso.
Primeiro você baixa o código fonte do repositório oficial (às vezes tendo que baixar um programa diferente de controle de versão), baixa todos os pacotes de desenvolvimento dependentes (quando eles dizem quais são), se familiariza com o código fonte (mesmo sem documentação), aprende uma nova linguagem e todas as APIs necessárias, tenta alterar e depois fica compilando até que tudo funcione (o que às vezes não ocorre).
Eu não sei vocês, mas só de imaginar tudo isso eu prefiro esperar e torcer pra que a funcionalidade que eu quero esteja disponível na versão seguinte.
Só que isso faz de você um mau menino, e os nerds olham feio pra você nos eventos de tecnologia e nas listas de discussão.
Abaixo você verá algumas etapas simples de como começar a alterar softwares utilizando os pacotes de source do Debian (Ubuntu ou qualquer derivado), através de passos reais que segui para adicionar uma pequena funcionalidade ao Transmission, mesmo sem conhecer nada de C, Gtk ou do código fonte do programa (na verdade, ainda nem comecei, estou escrevendo o post e fazendo).
Prepare-se para impressionar as gatinhas!
Baixando o código fonte e todas as dependências
Antes de começar qualquer projeto de pacote Debian em C, é fundamental que você tenha o essencial para construção instalado, que inclui compilador e biblioteca para criar pacotes, além do pacote fakeroot, necessário ao montar o pacote no final de todas as etapas:
/home/diogo/transmission# aptitude install build-essential fakeroot
Em seguida, baixe o fonte do programa que deseja alterar; no meu caso, o Transmission:
~/transmission$ apt-get source transmission
O comando apt-get source deve ser feito com o seu usuário mesmo, e não como root, apenas para baixar o código fonte no diretório atual.
Ao terminar, você terá os arquivos abaixo e todo o código fonte no diretório transmission-1.75:
~/transmission$ ls -l total 6568 drwxr-x--- 15 diogo diogo 4096 2009-10-03 13:01 transmission-1.75 -rw-r----- 1 diogo diogo 17251 2009-09-20 19:30 transmission_1.75-1.diff.gz -rw-r----- 1 diogo diogo 1519 2009-09-20 19:30 transmission_1.75-1.dsc -rw-r----- 1 diogo diogo 6681496 2009-09-20 19:30 transmission_1.75.orig.tar.gz
Depois, como root, é necessário instalar no sistema todas as bibliotecas necessárias para compilar o programa:
/home/diogo/transmission# apt-get build-dep transmission
Aqui foi necessário baixar 70 pacotes (27MB) com todos os cabeçalhos (arquivos .h) e demais dependências necessárias. Talvez você queira copiar a lista de pacotes que o apt-get mostrou para poder removê-los depois de compilar o programa.
Finalizando, apenas para concluir o fluxo, assim que quiser compilar o programa e montar um pacote, basta executar o comando abaixo, a partir do diretório dos arquivos fonte:
~/transmission/transmission-1.75$ dpkg-buildpackage -rfakeroot -uc -b
O pacote .deb será gerado no diretório acima e poderá ser instalado com dpkg -i nomedopacote.deb.
Alterando o código fonte
Já tenho o código fonte e já sei como montar o pacote, mas não faço a menor idéia de como alterar o programa. Tentemos assim mesmo.
Sabendo que o Transmission é um cliente de torrent, o recurso que quero adicionar é poder salvar os arquivos no mesmo diretório onde está localizado o .torrent. Para isso, a idéia é incluir uma opção na tela de configuração como na montagem abaixo em vermelho:

Antes de começar a bagunçar o código fonte que baixamos, é interessante fazer uma cópia do original para facilitar a geração de um patch depois:
~/transmission/transmission-1.75$ cp -r transmission-1.75/ transmission-1.75-original
Dando uma olhada no diretório do código fonte, temos o seguinte:
~/transmission/transmission-1.75$ ls -F
aclocal.m4 configure* gtk/ Makefile.am third-party/
AUTHORS configure.ac INSTALL Makefile.in transmission.spec.in
autogen.sh* COPYING install-sh* missing* Transmission.xcodeproj/
ChangeLog daemon/ libtransmission/ NEWS update-version-h.sh*
cli/ debian/ ltmain.sh* po/ web/
config.guess* depcomp* m4/ qt/
config.sub* doc/ macosx/ README
Como estou interessado em alterar a interface Gtk, vamos ao seu diretório:
~/transmission/transmission-1.75/gtk$ ls -F
actions.c icons.h sexy-icon-entry.c tr-core.h
actions.h lock.h sexy-icon-entry.h tr-icon.c
add-dialog.c logo.h sexy-marshal.c tr-icon.h
add-dialog.h main.c sexy-marshal.h tr-prefs.c
conf.c Makefile.am stats.c tr-prefs.h
conf.h Makefile.in stats.h tr-torrent.c
details.c makemeta-ui.c torrent-cell-renderer.c tr-torrent.h
details.h makemeta-ui.h torrent-cell-renderer.h tr-window.c
dialogs.c marshal.list tracker-list.c tr-window.h
dialogs.h msgwin.c tracker-list.h turtles.h
file-list.c msgwin.h transmission.1 ui.h
file-list.h notify.c transmission.desktop.in util.c
hig.c notify.h transmission.png util.h
hig.h options-icon.h tr-core.c
icons/ relocate.c tr-core-dbus.h
icons.c relocate.h tr-core-dbus.xml
Mesmo não conhecendo nada sobre a estrutura do programa, depois de dar uma olhada no nome dos arquivos, um bom palpite é que o arquivo conf.c esteja relacionado com o que preciso alterar, já que é o mais parecido com algo referente à janela de configuração. Edite-no-lo.
Como de praxe, o arquivo começa com alguns includes e em seguida passa para definições de funções. Todas estão com pelo menos uma linha de comentário e têm nomes legíveis. Até que um comentário chama atenção:
/**
* This is where we initialize the preferences file with the default values.
* If you add a new preferences key, you /must/ add a default value here.
*/
Vamos precisar adicionar uma preference key, para determinar se o novo checkbox da janela de configuração está marcado ou não. Então, melhor seguir a recomendação do comentário e definir um valor padrão para a nova chave:
~/transmission$ diff -u transmission-1.75-original/gtk/conf.c transmission-1.75/gtk/conf.c
--- transmission-1.75-original/gtk/conf.c 2009-10-03 14:03:28.000000000 -0300
+++ transmission-1.75/gtk/conf.c 2009-10-03 14:20:54.000000000 -0300
@@ -192,6 +192,7 @@
#endif
if( !str ) str = tr_getDefaultDownloadDir( );
tr_bencDictAddStr( d, TR_PREFS_KEY_DOWNLOAD_DIR, str );
+ tr_bencDictAddBool( d, PREF_KEY_DOWNLOAD_SAME_DIR, FALSE );
tr_bencDictAddBool( d, PREF_KEY_ASKQUIT, TRUE );
Se você não está familiarizado com o commando diff, basta saber que as primeiras linhas ali definem os arquivos comparados (o conf.c com o mesmo arquivo do diretório original), em seguida o numero da linha do trecho onde ocorreu a alteração, e logo abaixo a alteração: as linhas iniciadas com + foram adicionadas e com – removidas, as demais são apenas para referência da posição.
Contudo, a chave adicionada PREF_KEY_DOWNLOAD_SAME_DIR não existe ainda, foi inventada agora, e é necessário definí-la em algum lugar, que não sabemos onde. Meu primeiro palpite foi o arquivo conf.h, mas não tinha nada de relevante lá. Para não ter que ficar procurando arquivo por arquivo, vamos procurar onde uma outra chave qualquer já tenha sido definida:
~/transmission/transmission-1.75/gtk$ grep PREF_KEY_START * conf.c: tr_bencDictAddBool( d, PREF_KEY_START, TRUE ); tr-core.c: tr_ctorSetPaused( ctor, TR_FORCE, !pref_flag_get( PREF_KEY_START ) ); tr-core.c: const gboolean doStart = pref_flag_eval( start, PREF_KEY_START ); tr-prefs.c: w = new_check_button( s, PREF_KEY_START, core ); tr-prefs.h:#define PREF_KEY_START "start-added-torrents"
Pronto, agora sabemos que a definição de PREF_KEY_START (uma preferência de checkbox no mesmo lugar onde adicionaremos a nova) foi feita no arquivo tr-prefs.h. Vamos lá adicionar nossa chave nova também:
~/transmission$ diff -u transmission-1.75-original/gtk/tr-prefs.h transmission-1.75/gtk/tr-prefs.h
--- transmission-1.75-original/gtk/tr-prefs.h 2009-10-03 14:03:28.000000000 -0300
+++ transmission-1.75/gtk/tr-prefs.h 2009-10-03 14:38:55.000000000 -0300
@@ -26,6 +26,7 @@
#define PREF_KEY_INHIBIT_HIBERNATION "inhibit-desktop-hibernation"
#define PREF_KEY_DIR_WATCH "watch-dir"
#define PREF_KEY_DIR_WATCH_ENABLED "watch-dir-enabled"
+#define PREF_KEY_DOWNLOAD_SAME_DIR "download-same-dir"
#define PREF_KEY_SHOW_TRAY_ICON "show-notification-area-icon"
#define PREF_KEY_SHOW_DESKTOP_NOTIFICATION "show-desktop-notification"
#define PREF_KEY_START "start-added-torrents"
Ainda olhando o resultado do último grep, é possível observar que o arquivo tr-prefs.c também será útil, pois nele tem uma chamada a new_check_button, que apesar de não conhecer, dá a entender que cria um checkbox na janela de preferências. Editemos esse arquivo e adicionemos nosso checkbox novo:
~/transmission$ diff -u transmission-1.75-original/gtk/tr-prefs.c transmission-1.75/gtk/tr-prefs.c
--- transmission-1.75-original/gtk/tr-prefs.c 2009-10-03 14:03:28.000000000 -0300
+++ transmission-1.75/gtk/tr-prefs.c 2009-10-03 15:08:09.000000000 -0300
@@ -305,6 +305,10 @@
w = new_path_chooser_button( TR_PREFS_KEY_DOWNLOAD_DIR, core );
hig_workarea_add_row( t, &row, _( "Save to _Location:" ), w, NULL );
+ s = _( "Save to the same directory as torrent file" );
+ w = new_check_button( s, PREF_KEY_DOWNLOAD_SAME_DIR, core );
+ hig_workarea_add_wide_control( t, &row, w );
+
hig_workarea_add_section_divider( t, &row );
hig_workarea_add_section_title( t, &row, _( "Limits" ) );
O trecho adicionado foi baseado numa opção já existente. Para o que precisamos, não é necessário saber pra que servem as funções utilizadas, apesar de que em alguns minutos é possível descobrir isso e se familiarizar melhor com o que está acontecendo.
Bem, a princípio, o checkbox para configurar se queremos salvar os arquivos no mesmo diretório foi adicionado. Falta só utilizar o valor desta opção na janela de adicionar torrents.
Olhando novamente o diretório gtk, o provável arquivo que monta a janela que precisamos é o add-dialog.c. O que queremos nele é: caso o checkbox com a configuração PREF_KEY_DOWNLOAD_SAME_DIR esteja marcado, devemos usar o diretório do arquivo torrent ao invés do diretório configurado. Desta vez não teve muita mágica para encontrar onde mexer. A dica é ler as funções definidas e tentar identificar o que estamos procurando.
Dentro da função addSingleTorrentDialog no arquivo add-dialog.c foi possível encontrar o seguinte trecho:
if( tr_ctorGetDownloadDir( ctor, TR_FORCE, &str ) ) g_assert_not_reached( ); g_assert( str ); data = g_new0( struct AddData, 1 ); data->core = core; data->ctor = ctor; data->filename = g_strdup( tr_ctorGetSourceFile( ctor ) ); data->downloadDir = g_strdup( str );
Julgando pelos nomes, a função tr_ctorGetDownloadDir preenche o diretório de download do torrent da variável ctor na variável str.
O que precisamos então é definir a variável str para o diretório do arquivo torrent caso o checkbox PREF_KEY_DOWNLOAD_SAME_DIR esteja marcado.
Olhando o trecho de código anterior, sabemos que o caminho do arquivo pode ser obtido através da função tr_ctorGetSourceFile e, ainda no mesmo arquivo, é possível ver que consultamos se o checkbox da configuração está marcado com a função pref_flag_get. A única coisa que fica faltando é conseguir o nome do diretório a partir do caminho completo.
É comum que o diretório de um caminho completo seja retornado pela função ou comando chamado dirname. Como não sei nada de C pra ter certeza de como fazer, dei um grep por dirname para saber como o resto do programa obtém essa informação. Achei o cabeçalho libtransmission/utils.h que define a função tr_dirname. Tudo que eu precisava.
A alteração final, já incluindo o arquivo .h necessário para a função de diretório, ficou assim:
~/transmission$ diff -u transmission-1.75-original/gtk/add-dialog.c transmission-1.75/gtk/add-dialog.c --- transmission-1.75-original/gtk/add-dialog.c 2009-10-03 14:03:28.000000000 -0300 +++ transmission-1.75/gtk/add-dialog.c 2009-10-03 15:26:40.000000000 -0300 @@ -17,6 +17,7 @@ #include "file-list.h" #include "hig.h" #include "tr-prefs.h" +#include "libtransmission/utils.h" /**** ***** @@ -285,8 +286,12 @@ GTK_RESPONSE_CANCEL, -1 ); - if( tr_ctorGetDownloadDir( ctor, TR_FORCE, &str ) ) - g_assert_not_reached( ); + if ( pref_flag_get( PREF_KEY_DOWNLOAD_SAME_DIR ) ) { + str = tr_dirname( tr_strdup( tr_ctorGetSourceFile( ctor ) ) ); + } else { + if( tr_ctorGetDownloadDir( ctor, TR_FORCE, &str ) ) + g_assert_not_reached( ); + } g_assert( str ); data = g_new0( struct AddData, 1 );
O resto foi apenas baseado em código já existente.
Compilando e instalando o pacote
Para finalizar, basta deixar que o Debian compile e gere o pacote:
~/transmission/transmission-1.75$ dpkg-buildpackage -rfakeroot -uc -b
Em seguida, para instalar o pacote que foi gerado no diretório acima do diretório do código fonte, execute como root:
/home/diogo/transmission# dpkg -i transmission-gtk_1.75-1_amd64.deb
Gerando um patch
A forma universal de trocar alterações em código é através de patch, um arquivo com todos os diff’s mostrados acima.
Mas antes de criar o patch, é preciso remover alguns arquivos gerados pela compilação:
~/transmission/transmission-1.75$ dpkg-buildpackage -rfakeroot -Tclean
Agora é possível utilizar o diff para comparar o diretório original com o diretório onde as alterações foram feitas:
~/transmission$ diff -ru transmission-1.75-original/ transmission-1.75/ > download-same-directory.diff
Ao enviar o patch para alguém ou para simplesmente aplicá-lo no diretório original, basta fazer:
~/transmission/transmission-1.75-original$ patch -p1 < ../download-same-directory.diff
patching file gtk/add-dialog.c
patching file gtk/conf.c
patching file gtk/tr-prefs.c
patching file gtk/tr-prefs.h
Se você abrir o patch vai reparar que o caminho para os arquivos inicia com o nome dos diretórios transmission-1.75-original e transmission-1.75. O argumento -p1 do patch é justamente para ignorar esse primeiro pedaço do path, uma vez que já estamos no diretório onde as alterações devem ser aplicadas.
Considerações finais
Esta alteração, na verdade, foi extremamente simples e com fim didático. Para modificações reais, é fundamental ter mais dedicação para entender a estrutura do projeto e produzir um patch de qualidade, que eventualmente possa ser utilizado pelo autor. No caso mostrado, por exemplo, é provável que a melhor abordagem seria alterar o downloadDir do optional_args do tr_ctor, mas seria mais chato para descrever neste post.
Os passos acima são voltados mais para começar com pequenas alterações de forma prática, sem ter que participar ativamente do projeto.
Caso deseje realmente participar, a melhor forma continua sendo utilizar o repositório oficial e interagir com os colaboradores, até mesmo pra saber se a sua idéia já não está em desenvolvimento ou se você pode coloborar com tarefas mais importantes.
Além disso, é bem comum que os desenvolvedores não aceitem um patch porque ele foi feito baseado no código de uma versão anterior ao trunk / head (que é o caso dos releases de pacotes do Debian), não sendo mais possível aplicá-lo depois de tantas alterações na versão de desenvolvimento.
Já estou até imaginando a galera toda alterando seus programas agora… Boa sorte!




Show de bola hein, os teus palpites foram todos certeiros. :D
Quando veremos mais patches? :D
Nossa cara, muito legal!
Por favor, faça mais posts sobre isso.
Obrigado, um abraço.
Mágico, parabens.