<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>nfraprado</title><link href="https://nfraprado.net/pt-br/" rel="alternate"/><link href="https://nfraprado.net/feeds/all.atom.xml" rel="self"/><id>https://nfraprado.net/pt-br/</id><updated>2025-12-29T00:00:00-03:00</updated><entry><title>Retrospectiva de 2025 com camisetas</title><link href="https://nfraprado.net/pt-br/post/retrospectiva-de-2025-com-camisetas.html" rel="alternate"/><published>2025-12-29T00:00:00-03:00</published><updated>2025-12-29T00:00:00-03:00</updated><author><name>Nícolas F. R. A. Prado</name></author><id>tag:nfraprado.net,2025-12-29:/pt-br/post/retrospectiva-de-2025-com-camisetas.html</id><summary type="html">&lt;p&gt;Esse ano eu comprei mais camisetas do que qualquer outro ano. Em grande parte
porque eu decidi ir em todos os shows que eu pudesse.&lt;/p&gt;
&lt;p&gt;Com o ano se encerrando, esse me pareceu um bom momento pra rever as camisetas
que eu colecionei ao longo do ano e ver a …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Esse ano eu comprei mais camisetas do que qualquer outro ano. Em grande parte
porque eu decidi ir em todos os shows que eu pudesse.&lt;/p&gt;
&lt;p&gt;Com o ano se encerrando, esse me pareceu um bom momento pra rever as camisetas
que eu colecionei ao longo do ano e ver a história que elas contam. Então aqui
vai minha retrospectiva de 2025 por meio de camisetas:&lt;/p&gt;
&lt;img alt="{image}/shirts-collage.jpg" src="/images/2025-shirts/shirts-collage.jpg" /&gt;
&lt;p&gt;Seguindo a ordem da esquerda superior até a direita inferior:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Linha 1, Coluna 1: Março. Fui no show do Three Days Grace e Disturbed. Three
Days Grace era minha banda favorita na adolescência, e no dia do show eu
descobri que o Adam Gontier, o cantor, que tinha saído da banda muitos anos
antes, tinha acabado de voltar pra banda. Esse show foi um sonho realizado pro
meu adolescente interior.&lt;/li&gt;
&lt;li&gt;1-2: Março. Eu vi uma pessoa vestindo essa camiseta numa foto de um grupo de
voluntariado que eu participava e na hora perguntei onde eu podia comprar uma.
Inclusive eu comprei duas extras pra dar de presente pra amigos. Ela é de
longe minha camiseta mais popular, já recebeu elogios em muitas ocasiões e uma
vez até alguém pediu pra tirar foto dela. Segue o link da loja caso também
tenha se interessado:
&lt;a class="reference external" href="https://mtpfriends.bigcartel.com/product/what-s-more-punk-adult-t-shirt"&gt;https://mtpfriends.bigcartel.com/product/what-s-more-punk-adult-t-shirt&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;1-3: Abril. Fui num evento social no bairro e vi uma banda local que era bem
boa chamada Lolux.&lt;/li&gt;
&lt;li&gt;1-4: Maio. Fui no show do Bloc Party no Forest Hills Stadium. Eu saí dele um
pouco decepcionado com a qualidade do som mas foi divertido mesmo assim. Fora
que graças a esse show eu descobri Forest Hills, que é um bairro bem caricato
difícil de acreditar que faz parte da cidade de Nova Iorque (NYC).&lt;/li&gt;
&lt;li&gt;2-1: Junho. &lt;a class="reference external" href="https://www.wonderville.nyc/"&gt;Wonderville&lt;/a&gt; é simplesmente meu lugar favorito em NYC. Eles
definem o lugar como &amp;quot;O lar para jogos arcade feitos independentemente no
Brooklyn&amp;quot;. Ele tem uma estética bem estilo cyberpunk, todos os jogos podem ser
jogados de graça, e eles frequentemente fazem eventos únicos, desde
&lt;em&gt;livecoding&lt;/em&gt; (música gerada por código ao vivo), até &lt;em&gt;game jams&lt;/em&gt; e torneios de
beyblade. Essa camiseta foi feita pra comemorar o aniversário de 6 anos do
lugar. Acabei não indo na festa, mas peguei a camiseta algumas semanas depois
quando levei um amigo, que estava visitando NYC, lá para apresentar o lugar,
algo que já se tornou uma tradição pra mim (esse é o terceiro amigo visitante
que levo lá).&lt;/li&gt;
&lt;li&gt;2-2: Junho. Fui no show do Godspeed You! Black Emperor em Norwalk, CT. A
viagem de 1 hora de trem até uma cidade pequena contribuiu pra experiência. O
show foi muito único, o palco era muito escuro, projeções de filme em preto e
branco selecionadas a dedo eram passadas na parede de trás, e o som alternava
entre silêncios solitários e barulhos opressivos de guitarras e violinos
distorcidos. Escute &lt;a class="reference external" href="https://www.youtube.com/watch?v=cQcE4_7-X78"&gt;Sleep&lt;/a&gt;
para ter uma ideia do som, mas ver ao vivo foi outra coisa.&lt;/li&gt;
&lt;li&gt;2-3: Julho. Fui em um show ao vivo do Welcome to Night Vale no Brooklyn. Eu
escutava esse podcast durante a faculdade, então foi bem nostálgico ouvir de
novo, e ver ao vivo, com interações com a plateia, tornou ele ainda mais
divertido.&lt;/li&gt;
&lt;li&gt;2-4: Julho. Fui no show do Creed no Jones Beach Theater. A casa de show era
linda, ao ar livre e de frente para o mar, e foi ótimo ouvir Creed ao vivo.
Esse foi o primeiro show que eu fui com a minha mãe e o fato de termos ido de
trem e ficado em um hotel até o dia seguinte tornou esse passeio uma pequena
viagem de férias.&lt;/li&gt;
&lt;li&gt;3-1: Julho. Eu amo post-rock, e shows pequenos, então apesar de só ter
escutado um álbum do We Lost the Sea há muitos anos atrás, eu não podia perder
a oportunidade de vê-los de perto por apenas 25$. Foi incrível, esses shows
pequenos são os melhores.&lt;/li&gt;
&lt;li&gt;3-2: Agosto. Em contraste, pouco depois eu paguei 171$, os tickets de show
mais caros da minha vida, para ver Linkin Park no Prudential Center em Newark.
A vista era boa, mas meu assento era tão alto que eu me senti desconectado do
show. Além disso, apesar de essa camiseta ser, pela etiqueta, do mesmo tamanho
que eu sempre compro, ela é gigantesca.&lt;/li&gt;
&lt;li&gt;3-3: Agosto. Fui de novo no Forest Hills Stadium, dessa vez para ver Black
Keys com um amigo. Começou a chover bastante (irônico, dado o texto da
camiseta) pouco depois que eles começaram a tocar, mas por sorte parou de
chover a tempo para o show prosseguir, apesar de ter sido um pouco mais curto.&lt;/li&gt;
&lt;li&gt;3-4: Setembro. Fui ver Breaking Benjamin e Three Days Grace (de novo!). Apesar
de ser minha banda favorita na adolescência, ver TDG essa segunda vez foi
muito pior e me fez pensar que não vale a pena ver a mesma banda duas vezes.
Eles tocaram praticamente as mesmas músicas, e dessa vez tinham um telão atrás
mostrando visuais bem toscos claramente gerados por IA. Ainda bem que Breaking
Benjamin foi fantástico e fez o show valer a pena.&lt;/li&gt;
&lt;li&gt;4-1: Outubro. Na noite anterior de eu ir viajar, eu vi Anamanaguchi no
Brooklyn Steel. Eu só conhecia duas músicas deles, e eles só tocaram uma
delas, mas eu gostei bastante do álbum novo deles que conheci no show. Eles
também tocaram Hopes and Dreams do Undertale para homenagear o aniversário do
criador do jogo que era naquele dia, e como eu sou muito fã da música do
Undertale, isso tornou esse show ainda mais único e inesquecível.&lt;/li&gt;
&lt;li&gt;4-2: Outubro. A maior parte de outubro eu passei viajando de carro com um
amigo pela Califórnia como mencionei no &lt;a class="reference external" href="/pt-br/post/musica-do-mes-christopher-cross-sailing.html"&gt;artigo "Música do Mês: Christopher Cross - Sailing"&lt;/a&gt;. Foram
muitas maravilhas naturais, mas minha preferida foram as sequoias que vimos no
Parque Nacional das Sequoias. Por isso aproveitei pra passar na lojinha de
presentes onde comprei uma camiseta (e pins!) pra ter uma recordação desse
lugar incrível.&lt;/li&gt;
&lt;li&gt;4-3: Novembro. E para finalizar o ano, vi Of Monsters and Men no Brooklyn
Paramount com a minha namorada. O pit estava bem cheio então era um pouco
difícil de ver às vezes, mas o lugar era lindo e nos divertimos. Essa camiseta
é de manga longa, a primeira que eu compro em mais de dez anos, e é tão linda.
Acho que é minha camiseta favorita do ano.&lt;/li&gt;
&lt;/ul&gt;
</content><category term="2025"/><category term="roupa"/></entry><entry><title>Música do Mês: Andy Williams - Do You Hear What I Hear?</title><link href="https://nfraprado.net/pt-br/post/musica-do-mes-andy-williams-do-you-hear-what-i-hear.html" rel="alternate"/><published>2025-12-01T00:00:00-03:00</published><updated>2025-12-01T00:00:00-03:00</updated><author><name>Nícolas F. R. A. Prado</name></author><id>tag:nfraprado.net,2025-12-01:/pt-br/post/musica-do-mes-andy-williams-do-you-hear-what-i-hear.html</id><summary type="html">&lt;p&gt;Um pouco atrasado para novembro, mas antes tarde do que nunca!&lt;/p&gt;
&lt;p&gt;Com a chegada de novembro, e do clima natalino, eu coloquei para escutar uma &lt;a class="reference external" href="https://www.youtube.com/watch?v=g8UmqvOqB1A&amp;amp;list=PLBN3i8jrg4Wze3v_KZRPFmnQrTw5jia61&amp;amp;index=1"&gt;playlist de músicas antigas de natal no YouTube&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Muitas das músicas eu nunca tinha ouvido antes. Algumas das minhas favoritas foram &lt;a class="reference external" href="https://www.youtube.com/watch?v=lvVfIi0mQx4"&gt;Carol of the Bells …&lt;/a&gt;&lt;/p&gt;</summary><content type="html">&lt;p&gt;Um pouco atrasado para novembro, mas antes tarde do que nunca!&lt;/p&gt;
&lt;p&gt;Com a chegada de novembro, e do clima natalino, eu coloquei para escutar uma &lt;a class="reference external" href="https://www.youtube.com/watch?v=g8UmqvOqB1A&amp;amp;list=PLBN3i8jrg4Wze3v_KZRPFmnQrTw5jia61&amp;amp;index=1"&gt;playlist de músicas antigas de natal no YouTube&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Muitas das músicas eu nunca tinha ouvido antes. Algumas das minhas favoritas foram &lt;a class="reference external" href="https://www.youtube.com/watch?v=lvVfIi0mQx4"&gt;Carol of the Bells&lt;/a&gt;, &lt;a class="reference external" href="https://www.youtube.com/watch?v=oQ_2K6Jk8vs"&gt;The First Noel&lt;/a&gt; e &lt;a class="reference external" href="https://www.youtube.com/watch?v=4EvZOXEoJ84"&gt;Darlene Love - Christmas (Baby Please Come Home)&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Mas a minha favorita de todas é com certeza &amp;quot;Do You Hear What I Hear?&amp;quot;, especificamente a versão cantada por Andy Williams, então essa é minha música do mês:&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="https://www.youtube.com/watch?v=ksIZijfEuoY"&gt;Andy Williams - Do You Hear What I Hear?&lt;/a&gt;&lt;/p&gt;
</content><category term="2025"/><category term="musica-do-mes"/></entry><entry><title>Música do Mês: Christopher Cross - Sailing</title><link href="https://nfraprado.net/pt-br/post/musica-do-mes-christopher-cross-sailing.html" rel="alternate"/><published>2025-10-28T00:00:00-03:00</published><updated>2025-10-28T00:00:00-03:00</updated><author><name>Nícolas F. R. A. Prado</name></author><id>tag:nfraprado.net,2025-10-28:/pt-br/post/musica-do-mes-christopher-cross-sailing.html</id><summary type="html">&lt;p&gt;Esse mês eu e um amigo pegamos um carro e dirigimos pela Califórnia por duas
semanas. Nós navegamos no Lake Tahoe, andamos pelas dunas de areia no Death
Valley, e trilhamos entre as montanhas no Yosemite Valley. Nós vimos as árvores
mais velhas do mundo no Ancient Bristlecone Pine Forest …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Esse mês eu e um amigo pegamos um carro e dirigimos pela Califórnia por duas
semanas. Nós navegamos no Lake Tahoe, andamos pelas dunas de areia no Death
Valley, e trilhamos entre as montanhas no Yosemite Valley. Nós vimos as árvores
mais velhas do mundo no Ancient Bristlecone Pine Forest, e também as maiores, no
Sequoia National Park, em meio a uma neblina misteriosa.&lt;/p&gt;
&lt;p&gt;Nós ouvimos muitas músicas ao longo da viagem, mas houve uma delas que
aparentemente conquistou meu amigo imediatamente, já que ele colocou ela de novo
muitas vezes depois. É de fato uma linda música, e vai para sempre carregar a
memória dessa incrível viagem. É a minha escolha para esse mês:&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="https://www.youtube.com/watch?v=MEO6gYCFbr0"&gt;Christopher Cross - Sailing&lt;/a&gt;&lt;/p&gt;
&lt;img alt="{image}/general-sherman.jpg" src="/images/song-202510/general-sherman.jpg" /&gt;
</content><category term="2025"/><category term="musica-do-mes"/></entry><entry><title>Música do Mês: Moin - Lift You</title><link href="https://nfraprado.net/pt-br/post/musica-do-mes-moin-lift-you.html" rel="alternate"/><published>2025-09-30T00:00:00-03:00</published><updated>2025-09-30T00:00:00-03:00</updated><author><name>Nícolas F. R. A. Prado</name></author><id>tag:nfraprado.net,2025-09-30:/pt-br/post/musica-do-mes-moin-lift-you.html</id><summary type="html">&lt;p&gt;A minha escolha de música do mês dessa vez é uma música que tocou no &lt;a class="reference external" href="https://www.spectacletheater.com/"&gt;Spectacle Theater&lt;/a&gt; antes do filme começar (o filme era &lt;a class="reference external" href="https://www.spectacletheater.com/i-know-youre-out-there/#miragemen"&gt;Mirage Men (2013)&lt;/a&gt;, a propósito). A letra é bem poética, e a instrumentação tem um quê de surrealismo e mistério. É uma ótima música por si …&lt;/p&gt;</summary><content type="html">&lt;p&gt;A minha escolha de música do mês dessa vez é uma música que tocou no &lt;a class="reference external" href="https://www.spectacletheater.com/"&gt;Spectacle Theater&lt;/a&gt; antes do filme começar (o filme era &lt;a class="reference external" href="https://www.spectacletheater.com/i-know-youre-out-there/#miragemen"&gt;Mirage Men (2013)&lt;/a&gt;, a propósito). A letra é bem poética, e a instrumentação tem um quê de surrealismo e mistério. É uma ótima música por si só, mas ela foi elevada por ter sido tocada em um dos meus lugares favoritos em NYC e combinar perfeitamente com a sensação do momento.&lt;/p&gt;
&lt;p&gt;Confira:&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="https://m-o-i-n.bandcamp.com/track/lift-you-feat-sophia-al-maria"&gt;Moin - Lift You&lt;/a&gt;&lt;/p&gt;
</content><category term="2025"/><category term="musica-do-mes"/></entry><entry><title>Atualização da coleção de pins (128 pins em três imagens e uma tabela)</title><link href="https://nfraprado.net/pt-br/post/atualizacao-da-colecao-de-pins-128-pins-em-tres-imagens-e-uma-tabela.html" rel="alternate"/><published>2025-09-04T00:00:00-03:00</published><updated>2025-09-04T00:00:00-03:00</updated><author><name>Nícolas F. R. A. Prado</name></author><id>tag:nfraprado.net,2025-09-04:/pt-br/post/atualizacao-da-colecao-de-pins-128-pins-em-tres-imagens-e-uma-tabela.html</id><summary type="html">&lt;p&gt;Já fazem quase dois anos desde que eu publiquei o &lt;a class="reference external" href="/pt-br/post/colecionando-pins.html"&gt;artigo "Colecionando pins"&lt;/a&gt;! Já está
na hora de compartilhar uma atualização.&lt;/p&gt;
&lt;p&gt;De lá para cá eu decidi ser mais organizado e comecei uma planilha para
catalogar todos os detalhes da minha coleção de pins. A principal coisa que eu
queria …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Já fazem quase dois anos desde que eu publiquei o &lt;a class="reference external" href="/pt-br/post/colecionando-pins.html"&gt;artigo "Colecionando pins"&lt;/a&gt;! Já está
na hora de compartilhar uma atualização.&lt;/p&gt;
&lt;p&gt;De lá para cá eu decidi ser mais organizado e comecei uma planilha para
catalogar todos os detalhes da minha coleção de pins. A principal coisa que eu
queria ter é um histórico claro da coleção: quando, onde e como cada pin foi
adicionado à coleção. E depois de gastar bastante tempo preenchendo as lacunas,
hoje eu posso finalmente compartilhá-lo!&lt;/p&gt;
&lt;p&gt;Então aqui está, minha coleção de pins atual, composta por 128 pins ocupando
duas caixinhas e meia, resumida em três imagens e uma tabela:&lt;/p&gt;
&lt;img alt="{image}/display1.jpg" src="/images/pins-spreadsheet/display1.jpg" /&gt;
&lt;img alt="{image}/display2.jpg" src="/images/pins-spreadsheet/display2.jpg" /&gt;
&lt;img alt="{image}/display3.jpg" src="/images/pins-spreadsheet/display3.jpg" /&gt;
&lt;table border="1" class="docutils"&gt;
&lt;colgroup&gt;
&lt;col width="20%" /&gt;
&lt;col width="20%" /&gt;
&lt;col width="20%" /&gt;
&lt;col width="20%" /&gt;
&lt;col width="20%" /&gt;
&lt;/colgroup&gt;
&lt;thead valign="bottom"&gt;
&lt;tr&gt;&lt;th class="head"&gt;No.&lt;/th&gt;
&lt;th class="head"&gt;Nome&lt;/th&gt;
&lt;th class="head"&gt;Quando&lt;/th&gt;
&lt;th class="head"&gt;Onde&lt;/th&gt;
&lt;th class="head"&gt;Como&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.etsy.com/listing/587004849/poring-poporing-slime-monter-fanart-hard"&gt;Poring — Ragnarok Online&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2022-12-01&lt;/td&gt;
&lt;td&gt;Online&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.etsy.com/listing/587004849/poring-poporing-slime-monter-fanart-hard"&gt;Poporing — Ragnarok Online&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2022-12-01&lt;/td&gt;
&lt;td&gt;Online&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.etsy.com/listing/742804941/deviruchi-gg-ragnarok-online-enamel-pin"&gt;/Gg Emote — Ragnarok Online&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2022-12-16&lt;/td&gt;
&lt;td&gt;Online&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.etsy.com/listing/742804941/deviruchi-gg-ragnarok-online-enamel-pin"&gt;Deviruchi — Ragnarok Online&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2022-12-16&lt;/td&gt;
&lt;td&gt;Online&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.etsy.com/listing/842366471/?variation0=1473891219"&gt;Fairy Bottle — Zelda&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2022-12-16&lt;/td&gt;
&lt;td&gt;Online&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.etsy.com/listing/729815460/eeveelution-hard-enamel-pins-complete?variation0=1222773687"&gt;Eevee — Pokémon&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2022-12-21&lt;/td&gt;
&lt;td&gt;Online&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;L — Death Note&lt;/td&gt;
&lt;td&gt;2023-02-08&lt;/td&gt;
&lt;td&gt;Loja geek em Antuérpia, Bélgica&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;Phantom Of The Opera&lt;/td&gt;
&lt;td&gt;2023-02-13&lt;/td&gt;
&lt;td&gt;Show do Fantasma da Ópera no Majestic Theatre em NYC&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://store.moma.org/products/artist-enamel-pin-van-gogh"&gt;Van Gogh's The Starry Night&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2023-03-11&lt;/td&gt;
&lt;td&gt;MoMA em NYC&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.fangamer.com/collections/katamari-damacy/products/king-of-all-cosmos-pin-spinning"&gt;King — Katamari Damacy&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2023-03-23&lt;/td&gt;
&lt;td&gt;Estande da Fangamer na PAX East 23&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.fangamer.com/collections/katamari-damacy/products/katamari-damacy-the-prince-katamari-spinning-pin"&gt;Prince &amp;amp; Katamari — Katamari Damacy&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2023-03-23&lt;/td&gt;
&lt;td&gt;Estande da Fangamer na PAX East 23&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.fangamer.com/collections/undertale/products/undertale-annoying-dog-lapel-pin"&gt;Annoying Dog — Undertale&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2023-03-23&lt;/td&gt;
&lt;td&gt;Estande da Fangamer na PAX East 23&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.fangamer.com/collections/undertale/products/undertale-character-pins-set-1"&gt;Flowey — Undertale&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2023-03-23&lt;/td&gt;
&lt;td&gt;Estande da Fangamer na PAX East 23&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.fangamer.com/collections/undertale/products/undertale-character-pins-set-1"&gt;Sans — Undertale&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2023-03-23&lt;/td&gt;
&lt;td&gt;Estande da Fangamer na PAX East 23&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.fangamer.com/collections/undertale/products/undertale-character-pins-set-1"&gt;Frisk — Undertale&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2023-03-23&lt;/td&gt;
&lt;td&gt;Estande da Fangamer na PAX East 23&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.fangamer.com/collections/undertale/products/undertale-character-pins-set-2"&gt;Papyrus — Undertale&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2023-03-23&lt;/td&gt;
&lt;td&gt;Estande da Fangamer na PAX East 23&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;17&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.fangamer.com/collections/undertale/products/undertale-character-pins-set-1"&gt;Toriel — Undertale&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2023-03-23&lt;/td&gt;
&lt;td&gt;Estande da Fangamer na PAX East 23&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.fangamer.com/collections/undertale/products/undertale-character-pins-set-2"&gt;Undyne — Undertale&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2023-03-23&lt;/td&gt;
&lt;td&gt;Estande da Fangamer na PAX East 23&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;19&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.fangamer.com/collections/undertale/products/undertale-mercy-or-fight-enamel-pin-set"&gt;Mercy Button — Undertale&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2023-03-23&lt;/td&gt;
&lt;td&gt;Estande da Fangamer na PAX East 23&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.fangamer.com/collections/undertale/products/undertale-mercy-or-fight-enamel-pin-set"&gt;Fight Button — Undertale&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2023-03-23&lt;/td&gt;
&lt;td&gt;Estande da Fangamer na PAX East 23&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;21&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.fangamer.com/collections/stardew-valley/products/pinverse-junimos-pin-pack"&gt;Junimo Package — Stardew Valley&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2023-03-23&lt;/td&gt;
&lt;td&gt;Estande da Fangamer na PAX East 23&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;22&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://pinnydb.com/pinDetail/449"&gt;? Block — Super Mario Bros.&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2023-03-23&lt;/td&gt;
&lt;td&gt;Estande de mercadorias da PAX na PAX East 23&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;23&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://pinnydb.com/pinDetail/446"&gt;Mario — Super Mario Bros.&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2023-03-23&lt;/td&gt;
&lt;td&gt;Estande de mercadorias da PAX na PAX East 23&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;24&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://pinnydb.com/pinDetail/1362"&gt;East 2023 Logo&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2023-03-23&lt;/td&gt;
&lt;td&gt;Estande de mercadorias da PAX na PAX East 23&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;25&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://pinnydb.com/pinDetail/1365"&gt;PAX East VHS&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2023-03-23&lt;/td&gt;
&lt;td&gt;Estande de mercadorias da PAX na PAX East 23&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;26&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.neomerch.com/item.php?id=1294"&gt;Babaa — Neopets&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2023-03-24&lt;/td&gt;
&lt;td&gt;Estande da Geekify na PAX East 23&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;27&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.neomerch.com/item.php?id=2651"&gt;Wraith Paintbrush — Neopets&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2023-03-24&lt;/td&gt;
&lt;td&gt;Estande da Geekify na PAX East 23&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;28&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.neomerch.com/item.php?id=1287"&gt;Strawberry Fields Forever Paintbrush — Neopets&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2023-03-24&lt;/td&gt;
&lt;td&gt;Estande da Geekify na PAX East 23&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;29&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://pinnydb.com/pinDetail/279"&gt;Raz — Psychonauts&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2023-03-24..26&lt;/td&gt;
&lt;td&gt;Salão de trocas da PAX East 23&lt;/td&gt;
&lt;td&gt;Presente&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;30&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://pinnydb.com/pinDetail/1106"&gt;Kerbal Space Program&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2023-03-24..26&lt;/td&gt;
&lt;td&gt;Salão de trocas da PAX East 23&lt;/td&gt;
&lt;td&gt;Presente&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;31&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://pinnydb.com/pinDetail/271"&gt;Atlas And P-Body — Portal 2&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2023-03-24..26&lt;/td&gt;
&lt;td&gt;Salão de trocas da PAX East 23&lt;/td&gt;
&lt;td&gt;Troca&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;32&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://pinnydb.com/pinDetail/276"&gt;Diamond Ore Block — Minecraft&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2023-03-24..26&lt;/td&gt;
&lt;td&gt;Salão de trocas da PAX East 23&lt;/td&gt;
&lt;td&gt;Troca&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;33&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://pinnydb.com/pinDetail/703"&gt;Mae — Night In The Woods&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2023-03-24..26&lt;/td&gt;
&lt;td&gt;Estande da Finji na PAX East 23&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;34&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://pinnydb.com/pinDetail/921"&gt;Green Flame — Acquisitions Inc.&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2023-03-24..26&lt;/td&gt;
&lt;td&gt;Estande da Acquisitions Incorporated na PAX East 23&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;35&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.fangamer.com/products/raz-psychonauts-pin-spinning"&gt;Psychokinetic Raz — Psychonauts 2&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2023-03-24..26&lt;/td&gt;
&lt;td&gt;Estande da Fangamer na PAX East 23&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;36&lt;/td&gt;
&lt;td&gt;Goose — Kickstarter&lt;/td&gt;
&lt;td&gt;2023-03-24..26&lt;/td&gt;
&lt;td&gt;Estande do Kickstarter na PAX East 23&lt;/td&gt;
&lt;td&gt;Recompensa&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;37&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://pinnydb.com/pinDetail/1360"&gt;PAX Gold Mixtape&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2023-03-24..26&lt;/td&gt;
&lt;td&gt;Roleta no estande do Pinny Arcade na PAX East 23&lt;/td&gt;
&lt;td&gt;Recompensa&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;38&lt;/td&gt;
&lt;td&gt;Portuguese Tile&lt;/td&gt;
&lt;td&gt;2023-05-06..07&lt;/td&gt;
&lt;td&gt;Portugal (Porto?)&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;39&lt;/td&gt;
&lt;td&gt;Software Freedom Conservancy's Logo&lt;/td&gt;
&lt;td&gt;2023-05-08..11&lt;/td&gt;
&lt;td&gt;Encontro da Collabora em Portugal&lt;/td&gt;
&lt;td&gt;Presente (do Padovan)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;40&lt;/td&gt;
&lt;td&gt;Collabora's Logo&lt;/td&gt;
&lt;td&gt;2023-05-08..11&lt;/td&gt;
&lt;td&gt;Encontro da Collabora em Portugal&lt;/td&gt;
&lt;td&gt;Presente (do Guy)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;41&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.fangamer.com/products/dwarf-fortress-pin-finely-crafted"&gt;+Finely-Crafted Dwarven Pin+ — Dwarf Fortress&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2023-05-20&lt;/td&gt;
&lt;td&gt;Online&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;42&lt;/td&gt;
&lt;td&gt;Fairly Oddparents&lt;/td&gt;
&lt;td&gt;2023-06-06&lt;/td&gt;
&lt;td&gt;Online&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;43&lt;/td&gt;
&lt;td&gt;Loto&lt;/td&gt;
&lt;td&gt;2023-11-09..11&lt;/td&gt;
&lt;td&gt;Casa em NYC&lt;/td&gt;
&lt;td&gt;Presente (do Tony)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;44&lt;/td&gt;
&lt;td&gt;Ecuador&lt;/td&gt;
&lt;td&gt;2023-11-09..11&lt;/td&gt;
&lt;td&gt;Casa em NYC&lt;/td&gt;
&lt;td&gt;Presente (do Tony)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;45&lt;/td&gt;
&lt;td&gt;Los Andes&lt;/td&gt;
&lt;td&gt;2023-11-09..11&lt;/td&gt;
&lt;td&gt;Casa em NYC&lt;/td&gt;
&lt;td&gt;Presente (do Tony)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;46&lt;/td&gt;
&lt;td&gt;Canada Wilderness&lt;/td&gt;
&lt;td&gt;2023-11-09..11&lt;/td&gt;
&lt;td&gt;CN Tower em Toronto / Casa em NYC&lt;/td&gt;
&lt;td&gt;Presente (do Tony)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;47&lt;/td&gt;
&lt;td&gt;Paranaguá Train&lt;/td&gt;
&lt;td&gt;2023-11-09..11&lt;/td&gt;
&lt;td&gt;Casa em NYC&lt;/td&gt;
&lt;td&gt;Presente (do Tony)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;48&lt;/td&gt;
&lt;td&gt;Wonderville Arcade Machine&lt;/td&gt;
&lt;td&gt;2023-11-10&lt;/td&gt;
&lt;td&gt;Wonderville em NYC&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;49&lt;/td&gt;
&lt;td&gt;Mask — Mr. Robot&lt;/td&gt;
&lt;td&gt;2023-11-15&lt;/td&gt;
&lt;td&gt;Online&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;50&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.zenmonkeystudios.com/products/squidward-at-work-spongebob-squarepants-pin"&gt;Squidward At Work — Spongebob Squarepants&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2023-12-01&lt;/td&gt;
&lt;td&gt;Casa em NYC&lt;/td&gt;
&lt;td&gt;Presente (de Mãe)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;51&lt;/td&gt;
&lt;td&gt;Penguin&lt;/td&gt;
&lt;td&gt;2023-12-01&lt;/td&gt;
&lt;td&gt;Casa em NYC&lt;/td&gt;
&lt;td&gt;Presente (de Mãe)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;52&lt;/td&gt;
&lt;td&gt;Outback &amp;#64;&lt;/td&gt;
&lt;td&gt;2023-12-03 (originalmente ~2010)&lt;/td&gt;
&lt;td&gt;Casa em São Paulo&lt;/td&gt;
&lt;td&gt;Presente (de Mãe)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;53&lt;/td&gt;
&lt;td&gt;Outback Steakhouse Sign&lt;/td&gt;
&lt;td&gt;2023-12-03 (originalmente ~2010)&lt;/td&gt;
&lt;td&gt;Casa em São Paulo&lt;/td&gt;
&lt;td&gt;Presente (de Mãe)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;54&lt;/td&gt;
&lt;td&gt;Paper Boat&lt;/td&gt;
&lt;td&gt;2023-12-09..15&lt;/td&gt;
&lt;td&gt;Cruzeiro para o nordeste do Brasil&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;55&lt;/td&gt;
&lt;td&gt;Magic The Gathering Card Back&lt;/td&gt;
&lt;td&gt;2023-12-15..2024-01-27&lt;/td&gt;
&lt;td&gt;SoGo Plaza Shopping em São Paulo&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;56&lt;/td&gt;
&lt;td&gt;Odish — Pokémon&lt;/td&gt;
&lt;td&gt;2023-12-15..2024-01-27&lt;/td&gt;
&lt;td&gt;SoGo Plaza Shopping em São Paulo&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;57&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://topatoco.com/collections/pins-and-patches/products/kcg-thisfine-pins?variant=1867673042959"&gt;This Is Fine (First Panel)&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2024-05-29&lt;/td&gt;
&lt;td&gt;Online&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;58&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://topatoco.com/products/cpb-wtnv-pins01?_pos=2&amp;amp;_sid=ba14eae20&amp;amp;_ss=r&amp;amp;variant=30297717964911"&gt;All Hail The Glow Cloud — Welcome to Night Vale&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2024-05-29&lt;/td&gt;
&lt;td&gt;Online&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;59&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://topatoco.com/products/won-pins-01?variant=31246520451183"&gt;Spelcheck&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2024-05-29&lt;/td&gt;
&lt;td&gt;Online&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;60&lt;/td&gt;
&lt;td&gt;Donut&lt;/td&gt;
&lt;td&gt;2024-06-01..2024-08-01&lt;/td&gt;
&lt;td&gt;Loja de presentes no Bryant Park&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;61&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.pintrill.com/collections/peanuts/products/peanuts-originals-schroeder-pin"&gt;Schroeder — Peanuts&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2024-06-01..2024-08-01&lt;/td&gt;
&lt;td&gt;Loja Newtown HQ em NYC&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;62&lt;/td&gt;
&lt;td&gt;New York Pidgeon&lt;/td&gt;
&lt;td&gt;2024-06-01..2024-08-01&lt;/td&gt;
&lt;td&gt;Loja Newtown HQ em NYC&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;63&lt;/td&gt;
&lt;td&gt;New York Sewer&lt;/td&gt;
&lt;td&gt;2024-06-01..2024-08-01&lt;/td&gt;
&lt;td&gt;Loja Newtown HQ em NYC&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;64&lt;/td&gt;
&lt;td&gt;Groundon — Pokémon&lt;/td&gt;
&lt;td&gt;2024-06-01..2024-08-01&lt;/td&gt;
&lt;td&gt;Loja Say Cheese em NYC&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;65&lt;/td&gt;
&lt;td&gt;Pikachu — Pokémon&lt;/td&gt;
&lt;td&gt;2024-06-01..2024-08-01&lt;/td&gt;
&lt;td&gt;Loja Say Cheese em NYC&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;66&lt;/td&gt;
&lt;td&gt;MTA Logo&lt;/td&gt;
&lt;td&gt;2024-08-16&lt;/td&gt;
&lt;td&gt;New York Transit Museum&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;67&lt;/td&gt;
&lt;td&gt;Dollyinho&lt;/td&gt;
&lt;td&gt;2024-09-21&lt;/td&gt;
&lt;td&gt;Viena, Áustria&lt;/td&gt;
&lt;td&gt;Presente (do Tony)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;68&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.zenmonkeystudios.com/collections/spongebob-squarepants/products/imagination-spongebob-squarepants-pin"&gt;Imagination! — Spongebob Squarepants&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2024-10-17&lt;/td&gt;
&lt;td&gt;Estande do Zen Monkey Studios na New York Comic Con 2024&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;69&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.zenmonkeystudios.com/collections/spongebob-squarepants/products/mr-krabs-worlds-smallest-violin-spongebob-squarepants-pin"&gt;Mr. Krabs' World's Smallest Violin — Spongebob Squarepants&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2024-10-17&lt;/td&gt;
&lt;td&gt;Estande do Zen Monkey Studios na New York Comic Con 2024&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;70&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.zenmonkeystudios.com/collections/spongebob-squarepants/products/nail-in-head-patrick-spongebob-squarepants-pin"&gt;Nail in Head Patrick — Spongebob Squarepants&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2024-10-17&lt;/td&gt;
&lt;td&gt;Estande do Zen Monkey Studios na New York Comic Con 2024&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;71&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.zenmonkeystudios.com/collections/spongebob-squarepants/products/shocked-patrick-spongebob-squarepants-pin"&gt;Shocked Patrick — Spongebob Squarepants&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2024-10-17&lt;/td&gt;
&lt;td&gt;Estande do Zen Monkey Studios na New York Comic Con 2024&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;72&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.zenmonkeystudios.com/products/speedy-sonic-classic-sonic-the-hedgehog-collectible-pin"&gt;Speedy Sonic — Classic Sonic The Hedgehog&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2024-10-17&lt;/td&gt;
&lt;td&gt;Estande do Zen Monkey Studios na New York Comic Con 2024&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;73&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.zenmonkeystudios.com/products/pizza-drooling-gir-invader-zim-pin"&gt;Pizza Slurpin' Gir — Invader Zim&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2024-10-17&lt;/td&gt;
&lt;td&gt;Estande do Zen Monkey Studios na New York Comic Con 2024&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;74&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.zenmonkeystudios.com/products/aang-on-air-scooter-avatar-the-last-airbender-pin"&gt;Aang On Air Scooter — Avatar: The Last Airbender&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2024-10-17&lt;/td&gt;
&lt;td&gt;Estande do Zen Monkey Studios na New York Comic Con 2024&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;75&lt;/td&gt;
&lt;td&gt;Mr. Incredible — The Incredibles&lt;/td&gt;
&lt;td&gt;2024-10-17&lt;/td&gt;
&lt;td&gt;Disney pin trading event at New York Comic Con 2024&lt;/td&gt;
&lt;td&gt;Troca&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;76&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.pintradingdb.com/pin/58254"&gt;Woody, Buzz Lightyear &amp;amp; Jessie — Toy Story&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2024-10-17&lt;/td&gt;
&lt;td&gt;Estande de pins na New York Comic Con 2024&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;77&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.pintradingdb.com/pin/52091"&gt;Randall Boggs with Scream Canister — Monsters Inc.&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2024-10-17&lt;/td&gt;
&lt;td&gt;Estande de pins na New York Comic Con 2024&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;78&lt;/td&gt;
&lt;td&gt;The One Ring — The Lord of the Rings&lt;/td&gt;
&lt;td&gt;2024-10-17&lt;/td&gt;
&lt;td&gt;Estande de pins na New York Comic Con 2024&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;79&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://numskull.com/products/official-pac-man-9-pin-set"&gt;Pacman&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2024-10-17&lt;/td&gt;
&lt;td&gt;Estande de pins na New York Comic Con 2024&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;80&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.pintradingdb.com/pin/56930"&gt;Chicken Little&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2024-10-17&lt;/td&gt;
&lt;td&gt;Estande de pins na New York Comic Con 2024&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;81&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.hottopic.com/product/kirby-enamel-pin-set/33449656.html"&gt;Warp Star Kirby&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2024-10-17&lt;/td&gt;
&lt;td&gt;Estande de pins na New York Comic Con 2024&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;82&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.hottopic.com/product/kirby-enamel-pin-set/33449656.html"&gt;Kirby&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2024-10-17&lt;/td&gt;
&lt;td&gt;Estande de pins na New York Comic Con 2024&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;83&lt;/td&gt;
&lt;td&gt;Coraline&lt;/td&gt;
&lt;td&gt;2024-10-17&lt;/td&gt;
&lt;td&gt;Estande da Spooksieboo na New York Comic Con 2024&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;84&lt;/td&gt;
&lt;td&gt;BFF Frog&lt;/td&gt;
&lt;td&gt;2024-10-17&lt;/td&gt;
&lt;td&gt;Estande de mercadorias da NYCC na New York Comic Con 2024&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;85&lt;/td&gt;
&lt;td&gt;BFF Rat&lt;/td&gt;
&lt;td&gt;2024-10-17&lt;/td&gt;
&lt;td&gt;Estande de mercadorias da NYCC na New York Comic Con 2024&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;86&lt;/td&gt;
&lt;td&gt;NYCC&lt;/td&gt;
&lt;td&gt;2024-10-17&lt;/td&gt;
&lt;td&gt;Estande de mercadorias da NYCC na New York Comic Con 2024&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;87&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.etsy.com/listing/905102480/ouroboros-symbol-aluminum-metal-resin"&gt;Ouroboros — Fullmetal Alchemist&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2024-10-26&lt;/td&gt;
&lt;td&gt;Online&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;88&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.gallerynucleus.com/detail/23589/"&gt;Greg — Over The Garden Wall&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2024-10-28&lt;/td&gt;
&lt;td&gt;Online&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;89&lt;/td&gt;
&lt;td&gt;Spectacle Logo&lt;/td&gt;
&lt;td&gt;2024-11-02&lt;/td&gt;
&lt;td&gt;Spectacle Theater em NYC&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;90&lt;/td&gt;
&lt;td&gt;Crash Bandicoot&lt;/td&gt;
&lt;td&gt;2024-12-10&lt;/td&gt;
&lt;td&gt;Corrida de uber indo para um parque de trapolim em São Paulo&lt;/td&gt;
&lt;td&gt;Presente (da Marianna)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;91&lt;/td&gt;
&lt;td&gt;Black Cat In Bucket&lt;/td&gt;
&lt;td&gt;2024-12-10&lt;/td&gt;
&lt;td&gt;Corrida de uber indo para um parque de trapolim em São Paulo&lt;/td&gt;
&lt;td&gt;Presente (da Marianna)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;92&lt;/td&gt;
&lt;td&gt;Zé Gotinha&lt;/td&gt;
&lt;td&gt;2025-01-12&lt;/td&gt;
&lt;td&gt;Casa do Tony&lt;/td&gt;
&lt;td&gt;Presente (do Tony)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;93&lt;/td&gt;
&lt;td&gt;Tudor Rose&lt;/td&gt;
&lt;td&gt;2025-01-18&lt;/td&gt;
&lt;td&gt;Casa do Tony&lt;/td&gt;
&lt;td&gt;Presente (do Tony)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;94&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.icebrg.com.br/irmao"&gt;Irmão Do Jorel&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2025-01-20&lt;/td&gt;
&lt;td&gt;Loja Santa Hell na Galeria do Rock em São Paulo&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;95&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.icebrg.com.br/coco"&gt;Coco Mágico — Irmão do Jorel&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2025-01-20&lt;/td&gt;
&lt;td&gt;Loja Santa Hell na Galeria do Rock em São Paulo&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;96&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.icebrg.com.br/1963"&gt;Mônica 1963 — Turma da Mônica&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2025-01-20&lt;/td&gt;
&lt;td&gt;Loja Santa Hell na Galeria do Rock em São Paulo&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;97&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.icebrg.com.br/sansao"&gt;Sansão — Turma da Mônica&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2025-01-20&lt;/td&gt;
&lt;td&gt;Loja Santa Hell na Galeria do Rock em São Paulo&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;98&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.icebrg.com.br/ratinho"&gt;Ratinho — Castelo Rá Tim Bum&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2025-01-20&lt;/td&gt;
&lt;td&gt;Loja Santa Hell na Galeria do Rock em São Paulo&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;99&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.icebrg.com.br/senta"&gt;Senta Que Lá Vem História — Castelo Rá Tim Bum&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2025-01-20&lt;/td&gt;
&lt;td&gt;Loja Santa Hell na Galeria do Rock em São Paulo&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.icebrg.com.br/minhocao"&gt;Minhocão&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2025-01-20&lt;/td&gt;
&lt;td&gt;Loja Santa Hell na Galeria do Rock em São Paulo&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;101&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.icebrg.com.br/liberdade"&gt;Liberdade&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2025-01-20&lt;/td&gt;
&lt;td&gt;Loja Santa Hell na Galeria do Rock em São Paulo&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;102&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.icebrg.com.br/cavaquinho"&gt;Cavaquinho&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2025-01-20&lt;/td&gt;
&lt;td&gt;Loja Santa Hell na Galeria do Rock em São Paulo&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;103&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.icebrg.com.br/saojorge"&gt;São Jorge&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2025-01-20&lt;/td&gt;
&lt;td&gt;Loja Santa Hell na Galeria do Rock em São Paulo&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;104&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.icebrg.com.br/capivara"&gt;Capivara&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2025-01-20&lt;/td&gt;
&lt;td&gt;Loja Santa Hell na Galeria do Rock em São Paulo&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;105&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.icebrg.com.br/piso"&gt;Piso de Caquinho&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2025-01-20&lt;/td&gt;
&lt;td&gt;Loja Santa Hell na Galeria do Rock em São Paulo&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;106&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.icebrg.com.br/luckycat"&gt;Lucky Cat&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2025-01-30&lt;/td&gt;
&lt;td&gt;Online&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;107&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.icebrg.com.br/andorinha"&gt;Andorinha&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2025-01-30&lt;/td&gt;
&lt;td&gt;Online&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;108&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.icebrg.com.br/fliper"&gt;Fliperama&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2025-01-30&lt;/td&gt;
&lt;td&gt;Online&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;109&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.icebrg.com.br/bilhete"&gt;Metrô SP&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2025-01-30&lt;/td&gt;
&lt;td&gt;Online&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;110&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.icebrg.com.br/augusta"&gt;Rua Augusta&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2025-01-30&lt;/td&gt;
&lt;td&gt;Online&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;111&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.icebrg.com.br/brasil"&gt;Bandeira Brasil&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2025-01-30&lt;/td&gt;
&lt;td&gt;Online&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;112&lt;/td&gt;
&lt;td&gt;Tv Cultura&lt;/td&gt;
&lt;td&gt;2025-02-02&lt;/td&gt;
&lt;td&gt;Feira de rua perto da Av. Paulista em São Paulo&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;113&lt;/td&gt;
&lt;td&gt;Globo&lt;/td&gt;
&lt;td&gt;2025-02-02&lt;/td&gt;
&lt;td&gt;Feira de rua perto da Av. Paulista em São Paulo&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;114&lt;/td&gt;
&lt;td&gt;Linux Conectiva&lt;/td&gt;
&lt;td&gt;2025-02-02&lt;/td&gt;
&lt;td&gt;Feira de rua perto da Av. Paulista em São Paulo&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;115&lt;/td&gt;
&lt;td&gt;Windows 2000&lt;/td&gt;
&lt;td&gt;2025-02-02&lt;/td&gt;
&lt;td&gt;Feira de rua perto da Av. Paulista em São Paulo&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;116&lt;/td&gt;
&lt;td&gt;ABNT&lt;/td&gt;
&lt;td&gt;2025-02-02&lt;/td&gt;
&lt;td&gt;Feira de rua perto da Av. Paulista em São Paulo&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;117&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.etsy.com/listing/4302174034/daft-punk-pin-enamel-lapel-alive"&gt;Daft Punk&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2025-03-18&lt;/td&gt;
&lt;td&gt;Online&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;118&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.etsy.com/listing/1030452012/chrono-trigger-for-snes-chrono-trigger"&gt;Chrono Trigger&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2025-03-27&lt;/td&gt;
&lt;td&gt;Online&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;119&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.etsy.com/listing/1724710918/sims-plumbob-mini-enamel-pin"&gt;Plumbob — The Sims&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2025-03-28&lt;/td&gt;
&lt;td&gt;Online&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;120&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.etsy.com/listing/1570559383/cardcaptor-sealing-key-mini-enamel-pin"&gt;Cardcaptor Sealing Key — Cardcaptor Sakura&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2025-03-28&lt;/td&gt;
&lt;td&gt;Online&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;121&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.etsy.com/listing/1570493833/triforce-mini-enamel-pin"&gt;Triforce — Zelda&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2025-03-28&lt;/td&gt;
&lt;td&gt;Online&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;122&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.etsy.com/listing/1608102952/red-cloud-mini-enamel-pin"&gt;Akatsuki — Naruto&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2025-03-28&lt;/td&gt;
&lt;td&gt;Online&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;123&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.etsy.com/listing/1516361510/nostalgic-video-game-characters-pins-vol"&gt;Klonoa&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2025-03-28&lt;/td&gt;
&lt;td&gt;Online&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;124&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.compressmerch.com/collections/finji/products/love_from_wilmot_pinny"&gt;Love From Wilmot — Wilmot’s Warehouse&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2025-04-26&lt;/td&gt;
&lt;td&gt;Online&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;125&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.compressmerch.com/products/usual-june-pinny-arcade-enamel-pin?variant=44932087873698"&gt;Usual June&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2025-04-26&lt;/td&gt;
&lt;td&gt;Online&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;126&lt;/td&gt;
&lt;td&gt;Hope Hammer — Godspeed You! Black Emperor&lt;/td&gt;
&lt;td&gt;2025-06-27&lt;/td&gt;
&lt;td&gt;Show do Godspeed You Black Emperor no District Music Hall em Norwalk, CT&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;127&lt;/td&gt;
&lt;td&gt;Javascript&lt;/td&gt;
&lt;td&gt;2025-08-07&lt;/td&gt;
&lt;td&gt;Encontro do Astoria Tech Meetup&lt;/td&gt;
&lt;td&gt;Presente (de Tea)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;128&lt;/td&gt;
&lt;td&gt;&lt;a class="reference external" href="https://www.etsy.com/listing/1780904874/fez-gomez-1-enamel-pin-and-magnet"&gt;Gomez — FEZ&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2025-08-09&lt;/td&gt;
&lt;td&gt;Long Island Retro Gaming Expo no Cradle of Aviation Museum&lt;/td&gt;
&lt;td&gt;Compra&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;[*] O pin do logo da Collabora não está nas fotos porque ele é magnético, mas
você pode vê-lo no artigo anterior.&lt;/p&gt;
&lt;p&gt;A planilha completa não coube nessa página, então a tabela acima contém apenas
as principais colunas. Você pode baixar a planilha completa no link abaixo que
também inclui a autenticidade e fabricante do pin e comentários adicionais:&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="https://nfraprado.net/pt-br/files/pins-spreadsheet/pins.ods"&gt;Planilha de pins&lt;/a&gt;&lt;/p&gt;
</content><category term="2025"/><category term="pin"/></entry><entry><title>Música do Mês: Soundgarden - Black Hole Sun</title><link href="https://nfraprado.net/pt-br/post/musica-do-mes-soundgarden-black-hole-sun.html" rel="alternate"/><published>2025-08-31T00:00:00-03:00</published><updated>2025-08-31T00:00:00-03:00</updated><author><name>Nícolas F. R. A. Prado</name></author><id>tag:nfraprado.net,2025-08-31:/pt-br/post/musica-do-mes-soundgarden-black-hole-sun.html</id><summary type="html">&lt;p&gt;Anteontem eu fui numa experiência teatral imersiva chamada &lt;a class="reference external" href="https://www.theshed.org/program/444-violas-room"&gt;Viola's Room&lt;/a&gt;. Ela começou no quarto de uma menina, com luzes fracas e algumas camas no chão como em uma festa do pijama. Nos fones de ouvido, uma linda música começou a tocar. O momento foi tão mágico que é difícil de …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Anteontem eu fui numa experiência teatral imersiva chamada &lt;a class="reference external" href="https://www.theshed.org/program/444-violas-room"&gt;Viola's Room&lt;/a&gt;. Ela começou no quarto de uma menina, com luzes fracas e algumas camas no chão como em uma festa do pijama. Nos fones de ouvido, uma linda música começou a tocar. O momento foi tão mágico que é difícil de descrever. Eu me senti como uma criança de novo por um segundo. E eu soube que eu sempre lembraria desse momento no futuro (&lt;a class="reference external" href="https://www.thedictionaryofobscuresorrows.com/concept/des-vu"&gt;dès vu&lt;/a&gt;). Se você tiver a chance de ir, eu recomendo demais.&lt;/p&gt;
&lt;p&gt;De qualquer forma, a música que estava tocando, que eu descobri mais tarde, é a minha música do mês para agosto:&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="https://www.youtube.com/watch?v=3mbBbFH9fAg"&gt;Soundgarden - Black Hole Sun&lt;/a&gt;&lt;/p&gt;
</content><category term="2025"/><category term="musica-do-mes"/></entry><entry><title>Música do Mês: Junko Yagami - Bay City</title><link href="https://nfraprado.net/pt-br/post/musica-do-mes-junko-yagami-bay-city.html" rel="alternate"/><published>2025-08-12T00:00:00-03:00</published><updated>2025-08-12T00:00:00-03:00</updated><author><name>Nícolas F. R. A. Prado</name></author><id>tag:nfraprado.net,2025-08-12:/pt-br/post/musica-do-mes-junko-yagami-bay-city.html</id><summary type="html">&lt;p&gt;Hoje eu quero tentar começar algo novo. Todo mês eu vou compartilhar uma música
que se destacou pra mim durante o mês.&lt;/p&gt;
&lt;p&gt;Pra essa primeira, apesar de já estar no meio de agosto, eu sinto que eu preciso
compartilhar essa música que marcou meus dias de julho e inclusive junho …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Hoje eu quero tentar começar algo novo. Todo mês eu vou compartilhar uma música
que se destacou pra mim durante o mês.&lt;/p&gt;
&lt;p&gt;Pra essa primeira, apesar de já estar no meio de agosto, eu sinto que eu preciso
compartilhar essa música que marcou meus dias de julho e inclusive junho. Muitas
das minhas caminhadas pela cidade em dias ensolarados foram acompanhadas por
ela, fazendo dessa música o hino desse verão pra mim.&lt;/p&gt;
&lt;p&gt;Sem mais delongas, minha música do mês pra julho é&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="https://www.youtube.com/watch?v=QLHMhVonF-s"&gt;Junko Yagami - Bay City&lt;/a&gt;&lt;/p&gt;
</content><category term="2025"/><category term="musica-do-mes"/></entry><entry><title>Um novo tema para o blog (agora com 0% JavaScript!)</title><link href="https://nfraprado.net/pt-br/post/um-novo-tema-para-o-blog-agora-com-0-javascript.html" rel="alternate"/><published>2025-05-04T00:00:00-03:00</published><updated>2025-05-04T00:00:00-03:00</updated><author><name>Nícolas F. R. A. Prado</name></author><id>tag:nfraprado.net,2025-05-04:/pt-br/post/um-novo-tema-para-o-blog-agora-com-0-javascript.html</id><summary type="html">&lt;p&gt;Recentemente eu atualizei o tema do blog e achei que seria bom escrever um
artigo não só para registrar a razão mas também para arquivar fotos do blog
antes e depois da mudança para o futuro.&lt;/p&gt;
&lt;p&gt;Quando eu criei esse blog, eu escolhi o &lt;a class="reference external" href="https://github.com/gunchu/nikhil-theme"&gt;nikhil-theme&lt;/a&gt; como tema por ser
minimalista …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Recentemente eu atualizei o tema do blog e achei que seria bom escrever um
artigo não só para registrar a razão mas também para arquivar fotos do blog
antes e depois da mudança para o futuro.&lt;/p&gt;
&lt;p&gt;Quando eu criei esse blog, eu escolhi o &lt;a class="reference external" href="https://github.com/gunchu/nikhil-theme"&gt;nikhil-theme&lt;/a&gt; como tema por ser
minimalista e bonito. (Mais bonito ainda depois que troquei as fontes para serem
iguais às do blog &lt;a class="reference external" href="https://www.thirtythreeforty.net/"&gt;https://www.thirtythreeforty.net/&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;Porém algum tempo depois conforme eu estava olhando os arquivos de &lt;em&gt;template&lt;/em&gt;
HTML do tema, eu fiquei surpreso ao descobrir que esse tema tão simples continha
JavaScript. Eu tentei carregar a página com o JavaScript removido e percebi que
ele era necessário apenas para expandir o menu hambúrguer em dispositivos
móveis. Isso me incomodou bastante. Um simples blog como esse não deveria
precisar de JavaScript nenhum. E o mais irritante é ele ser necessário por causa
de uma única funcionalidade.&lt;/p&gt;
&lt;p&gt;Mas na hora eu já estava fazendo outras mudanças no blog e não estava a fim
de ter que mudar de tema também, então deixei essa tarefa para o futuro, até
recentemente, quando eu finalmente decidi tomar alguma atitude.&lt;/p&gt;
&lt;p&gt;Para decidir qual seria o novo tema do blog, eu comecei olhando em
&lt;a class="reference external" href="https://github.com/getpelican/pelican-themes"&gt;pelican-themes&lt;/a&gt; (o mesmo lugar onde eu originalmente descobri o nikhil-theme),
mas só achei um ou outro tema que não usava JavaScript e eles não me
interessaram.&lt;/p&gt;
&lt;p&gt;Eventualmente eu lembrei do blog &lt;a class="reference external" href="https://seirdy.one/"&gt;seirdy.one&lt;/a&gt;. É um blog que eu acompanho que tem
artigos excelentes e ele tem &lt;a class="reference external" href="https://seirdy.one/meta/site-design/"&gt;uma página inteira sobre as normas de design&lt;/a&gt;
que o site segue. Esses normas focam em minimalismo, compatibilidade e
acessibilidade, que são todas qualidades que eu gostaria para o meu blog, então
eu escolhi adotar o tema do Seirdy para o meu blog.&lt;/p&gt;
&lt;p&gt;Olhando pelo &lt;a class="reference external" href="https://git.sr.ht/~seirdy/seirdy.one"&gt;código fonte do blog seirdy&lt;/a&gt; eu notei que ele usa o gerador de
site estático &lt;a class="reference external" href="https://gohugo.io/"&gt;Hugo&lt;/a&gt; enquanto o meu usa &lt;a class="reference external" href="https://getpelican.com/"&gt;pelican&lt;/a&gt;, então adaptar esse tema não
seria só questão de copiar e colar.&lt;/p&gt;
&lt;p&gt;Idealmente eu teria revisado os &lt;em&gt;templates&lt;/em&gt; HTML do Seirdy e entendido todos eles
para conseguir adaptá-los para os &lt;em&gt;templates&lt;/em&gt; HTML em Jinja2 para o pelican, mas
honestamente eu não estava com toda essa energia. Então eu fui pela solução
preguiçosa: eu copiei os arquivos CSS e ajustei meus &lt;em&gt;templates&lt;/em&gt; até o site
aparentar razoável. Ou seja, eu provavelmente não estou seguindo várias das
normas do Seirdy, mas pelo menos foi o suficiente para deixar o site mais leve,
sem nenhum JS (e nem CSS externo), que era o objetivo principal.&lt;/p&gt;
&lt;p&gt;A mudança de código que resultou disso pode ser vista &lt;a class="reference external" href="https://codeberg.org/nfraprado/blog/commit/4be0e1d84495f3186a0811f1d184062e63fe1f21"&gt;nesse commit&lt;/a&gt; (e também
&lt;a class="reference external" href="https://codeberg.org/nfraprado/blog/commit/6f82039f935841dc1fc09c968c48f796528e5123"&gt;nesse&lt;/a&gt; e &lt;a class="reference external" href="https://codeberg.org/nfraprado/blog/commit/ff36f3cdb1e1bd3fcb62763afccecdbf9ca31508"&gt;nesse&lt;/a&gt; para consertos posteriores).&lt;/p&gt;
&lt;p&gt;Já quanto à mudança visual do site, as fotos abaixo mostram as diferentes
páginas antes e depois da mudança do tema, respectivamente.&lt;/p&gt;
&lt;p&gt;Página principal:&lt;/p&gt;
&lt;img alt="{image}/before-home.png" src="/images/blog-theme-seirdy-update/before-home.png" /&gt;
&lt;img alt="{image}/after-home.png" src="/images/blog-theme-seirdy-update/after-home.png" /&gt;
&lt;p&gt;Um artigo:&lt;/p&gt;
&lt;img alt="{image}/before-post.png" src="/images/blog-theme-seirdy-update/before-post.png" /&gt;
&lt;img alt="{image}/after-post.png" src="/images/blog-theme-seirdy-update/after-post.png" /&gt;
&lt;p&gt;Categorias:&lt;/p&gt;
&lt;img alt="{image}/before-tags.png" src="/images/blog-theme-seirdy-update/before-tags.png" /&gt;
&lt;img alt="{image}/after-tags.png" src="/images/blog-theme-seirdy-update/after-tags.png" /&gt;
&lt;p&gt;Sobre:&lt;/p&gt;
&lt;img alt="{image}/before-about.png" src="/images/blog-theme-seirdy-update/before-about.png" /&gt;
&lt;img alt="{image}/after-about.png" src="/images/blog-theme-seirdy-update/after-about.png" /&gt;
&lt;p&gt;Não vou mentir, eu já me sinto nostálgico e um pouco triste de ver o tema antigo
ir embora, mas eu acho que tornar o site mais minimalista vale a pena.&lt;/p&gt;
&lt;p&gt;Uma coisa perdida nessa transição foi o &lt;em&gt;syntax highlight&lt;/em&gt; para blocos de
código. Eu suponho que seja intencionalmente evitado pelo Seirdy por conta de
preocupações com a acessibilidade, mas eu deveria confirmar isso eventualmente e
se não for o caso, considerar adicioná-lo de volta.&lt;/p&gt;
&lt;p&gt;Apesar de ainda restarem detalhes para acertar, eu estou bem feliz com a mudança
e particularmente aliviado de não ter mais JavaScript rodando quando alguém vê
esse blog.&lt;/p&gt;
</content><category term="2025"/><category term="blog"/></entry><entry><title>Em busca do colar de pins perfeito</title><link href="https://nfraprado.net/pt-br/post/em-busca-do-colar-de-pins-perfeito.html" rel="alternate"/><published>2024-11-16T00:00:00-03:00</published><updated>2024-11-16T00:00:00-03:00</updated><author><name>Nícolas F. R. A. Prado</name></author><id>tag:nfraprado.net,2024-11-16:/pt-br/post/em-busca-do-colar-de-pins-perfeito.html</id><summary type="html">&lt;p&gt;Ano passado, no &lt;a class="reference external" href="/pt-br/post/colecionando-pins.html"&gt;artigo "Colecionando pins"&lt;/a&gt;, eu mencionei que eu geralmente uso meus
pins na touca, mas que ainda estava procurando uma boa alternativa para dias
quentes.&lt;/p&gt;
&lt;p&gt;Depois de pensar mais um pouco, eu concluí que um colar seria uma boa opção. Ele
não depende de nenhuma peça de roupa …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Ano passado, no &lt;a class="reference external" href="/pt-br/post/colecionando-pins.html"&gt;artigo "Colecionando pins"&lt;/a&gt;, eu mencionei que eu geralmente uso meus
pins na touca, mas que ainda estava procurando uma boa alternativa para dias
quentes.&lt;/p&gt;
&lt;p&gt;Depois de pensar mais um pouco, eu concluí que um colar seria uma boa opção. Ele
não depende de nenhuma peça de roupa específica e é bem visível, tanto para os
outros quanto para mim.&lt;/p&gt;
&lt;p&gt;Eu comecei a pesquisar por colares de pin e encontrei algumas soluções para o
conversor de pin para colar:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.epbot.com/2020/04/quick-craft-turn-any-disney-pin-into.html"&gt;Guia DIY: Conversor usando tarraxa de borracha&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.etsy.com/listing/1672516454/clearrings-necklace-converter-enamel-pin"&gt;Comprar: Conversor usando plástico transparente&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.etsy.com/listing/607824060/enamel-pin-necklace-converter-wear-your?show_sold_out_detail=1&amp;amp;ref=nla_listing_details"&gt;Comprar: Conversor usando um pedaço de couro&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://cosmicmedium.com/products/lapel-pin-necklace-lock-converter?variant=40404834123973"&gt;Comprar: Conversor usando tarraxa com trava&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;O guia é ótimo, mas eu não acho que tarraxas de borracha são seguras o
suficiente. E eu não achei o conversor de plástico nem o de couro bonitos. Mas o
de tarraxa com trava pareceu perfeito! Então eu comprei um na hora.&lt;/p&gt;
&lt;p&gt;Na verdade, eu comprei dois. O motivo é que alguns pins tem dois pinos, um do
lado do outro, então duas tarraxas seriam necessárias para deixar esses pins
orientados corretamente no colar (veja as fotos no final). Comprando dois
colares eu poderia transferir o conversor de um deles para o outro e ter um
colar com dois conversores, o que me permitiria usar qualquer pin da minha
coleção.&lt;/p&gt;
&lt;p&gt;Porém assim que o colar chegou e eu comecei a usá-lo, ficou claro que ele tinha
um grande problema: o pin ficava virando o tempo todo! Isso acaba com o
propósito do colar. Eu concluí que o problema era a corrente, que tinha que ser
trocada por algo mais rígido, como um cordão de couro. Eu escolhi este colar de
couro trançado (a variante de 20 polegadas de comprimento e 3 milímetros de
diâmetro):
&lt;a class="reference external" href="https://www.amazon.com/Flexible-Braided-Leather-Necklace-Pendants/dp/B095W7J7D7"&gt;https://www.amazon.com/Flexible-Braided-Leather-Necklace-Pendants/dp/B095W7J7D7&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Assim que ele chegou, eu peguei dois alicates, dois anéis de metal próprios para
bijuteria (já que os originais não iriam fechar nesse colar mais grosso) e usei
eles para colocar os dois conversores de pin no novo colar. E funcionou
perfeitamente, os pins não viraram mais! Eu também gostei mais da nova
aparência, a corrente era muito brilhante e fina para o meu gosto.&lt;/p&gt;
&lt;p&gt;Além disso, como os anéis que eu usei são bem mais largos que o colar, é muito
fácil de tirar e pôr os conversores de pin. Graças a isso, quando eu estou
usando um pin que só usa um conversor, eu tiro o outro para não deixar ele
pendurado fazendo barulho.&lt;/p&gt;
&lt;p&gt;Finalmente, aqui estão as fotos do resultado final:&lt;/p&gt;
&lt;p&gt;Pin de um pino:&lt;/p&gt;
&lt;img alt="{image}/df-front.jpg" src="/images/pin-necklace/df-front.jpg" /&gt;
&lt;img alt="{image}/df-back.jpg" src="/images/pin-necklace/df-back.jpg" /&gt;
&lt;p&gt;Pin de dois pinos:&lt;/p&gt;
&lt;img alt="{image}/lotr-front.jpg" src="/images/pin-necklace/lotr-front.jpg" /&gt;
&lt;img alt="{image}/lotr-back.jpg" src="/images/pin-necklace/lotr-back.jpg" /&gt;
&lt;p&gt;Colar no corpo:&lt;/p&gt;
&lt;img alt="{image}/df-wear.jpg" src="/images/pin-necklace/df-wear.jpg" /&gt;
&lt;img alt="{image}/lotr-wear.jpg" src="/images/pin-necklace/lotr-wear.jpg" /&gt;
&lt;p&gt;O único problema é que os pins de um pino, como o do Dwarf Fortress acima,
sempre ficam virados para o lado. Mas eu acho que não tem como evitar isso em um
colar. De qualquer forma, é só um detalhe e eu estou extremamente feliz com o
resultado. Eu tenho usado pins todo dia desde então!&lt;/p&gt;
</content><category term="2024"/><category term="pin"/></entry><entry><title>CardOS: Agora compilando sem Arduino!</title><link href="https://nfraprado.net/pt-br/post/cardos-agora-compilando-sem-arduino.html" rel="alternate"/><published>2024-07-31T00:00:00-03:00</published><updated>2024-07-31T00:00:00-03:00</updated><author><name>Nícolas F. R. A. Prado</name></author><id>tag:nfraprado.net,2024-07-31:/pt-br/post/cardos-agora-compilando-sem-arduino.html</id><summary type="html">&lt;p&gt;No &lt;a class="reference external" href="/pt-br/post/cardos-escrevendo-um-so-para-o-cardputer.html"&gt;artigo "CardOS: Escrevendo um SO para o Cardputer"&lt;/a&gt; eu falei sobre o SO que eu estou escrevendo para o
Cardputer e que o próximo passo era remover a dependência da toolchain do
Arduino. Levou dois meses mas finalmente eu consegui. O produto final foi &lt;a class="reference external" href="https://codeberg.org/nfraprado/cardOS/commit/85553f797cf0877e4c88a8e549052a445d00e7d1"&gt;esse
commit&lt;/a&gt; mas eu gostaria …&lt;/p&gt;</summary><content type="html">&lt;p&gt;No &lt;a class="reference external" href="/pt-br/post/cardos-escrevendo-um-so-para-o-cardputer.html"&gt;artigo "CardOS: Escrevendo um SO para o Cardputer"&lt;/a&gt; eu falei sobre o SO que eu estou escrevendo para o
Cardputer e que o próximo passo era remover a dependência da toolchain do
Arduino. Levou dois meses mas finalmente eu consegui. O produto final foi &lt;a class="reference external" href="https://codeberg.org/nfraprado/cardOS/commit/85553f797cf0877e4c88a8e549052a445d00e7d1"&gt;esse
commit&lt;/a&gt; mas eu gostaria de
descrever o processo para chegar até aqui neste artigo.&lt;/p&gt;
&lt;p&gt;Eu comecei rodando os comandos do Arduino para compilar e gravar com saída
&lt;em&gt;verbose&lt;/em&gt; habilitada e salvando essa saída em um arquivo para usar de
referência. Assim eu conseguiria ver exatamente o que o Arduino estava fazendo e
entender as etapas necessárias para ir do código-fonte até o binário gravado na
memória da placa.&lt;/p&gt;
&lt;p&gt;Então eu fiz as mudanças óbvias para converter de Arduino para C: Eu
renomeei o único arquivo de código-fonte do projeto de &lt;code class="docutils literal"&gt;cardOS.ino&lt;/code&gt; para
&lt;code class="docutils literal"&gt;cardOS.c&lt;/code&gt;, converti as funções &lt;code class="docutils literal"&gt;setup&lt;/code&gt; e &lt;code class="docutils literal"&gt;loop&lt;/code&gt; em uma função &lt;code class="docutils literal"&gt;main&lt;/code&gt;, e
mudei a etapa de compilação no meu Makefile para ao invés de chamar o Arduino,
chamar o compilador cruzado de C para a ESP32 (&lt;code class="docutils literal"&gt;xtensa-esp32s3-elf-gcc&lt;/code&gt;), que
já havia sido instalado na minha máquina pelo Arduino e cujo caminho eu descobri
pela saída do Arduino.&lt;/p&gt;
&lt;p&gt;Em seguida eu rodei a compilação, e consertei cada erro que o compilador gerou.
Especificamente, eu tive que adicionar alguns includes, protótipos de funções,
mudar o jeito que algumas variáveis eram definidas e passar o parâmetro
&lt;code class="docutils literal"&gt;-fno-builtin&lt;/code&gt; para o compilador para que ele me deixasse definir minhas
próprias funções da stdlib. Feito isso, eu agora tinha um arquivo &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Executable_and_Linkable_Format"&gt;ELF&lt;/a&gt; e
precisava descobrir como gravar ele na placa.&lt;/p&gt;
&lt;p&gt;Lendo por cima a saída do Arduino, eu aprendi que &lt;code class="docutils literal"&gt;esptool.py&lt;/code&gt; era o comando
usado para converter o arquivo ELF em um binário, e que ele era chamado de novo
para gravar o binário na placa. Além do código da aplicação, outras coisas
também estavam sendo gravadas: um bootloader e uma tabela de partição. Para não
complicar, eu fiz um pequeno teste para verificar se eu podia ignorar eles por
enquanto (ou seja, presumir que eles já estavam gravados): Eu fiz uma pequena
mudança no código do SO e usei a toolchain do Arduino para compilar e gravar
apenas ele e vi que a mudança apareceu no Cardputer, então a resposta era sim.&lt;/p&gt;
&lt;p&gt;Com isso em mente, eu atualizei o Makefile para chamar o &lt;code class="docutils literal"&gt;esptool.py&lt;/code&gt; para
converter o ELF gerado pelo compilador e então gravar na placa. A esta altura,
eu já tinha resolvido (eu achava) todo o procedimento para ir do código-fonte
até o binário gravado na placa, então eu rodei ele com &lt;code class="docutils literal"&gt;make upload&lt;/code&gt;. Mas não
funcionou, a tela no Cardputer nem ligava.&lt;/p&gt;
&lt;p&gt;Eu percebi que presumir que todo o código fosse funcionar de cara no novo
procedimento era muito otimista e decidi simplificar o teste o máximo possível:
Eu mudei o código na &lt;code class="docutils literal"&gt;main&lt;/code&gt; para apenas ligar o pino GPIO1 e conectei o pino a
um LED. Mas ainda assim, o LED não ligou.&lt;/p&gt;
&lt;p&gt;Foi aqui que eu fiquei travado por um tempo. O problema estava claramente no
binário da aplicação em algum lugar. Minhas duas teorias eram que ou o binário
em si tinha algum problema, ou algum código de inicialização estava faltando, já
que o Arduino incluia vários arquivos adicionais durante a compilação.&lt;/p&gt;
&lt;div class="section" id="configurando-os-enderecos-no-arquivo-elf"&gt;
&lt;h2&gt;Configurando os endereços no arquivo ELF&lt;/h2&gt;
&lt;p&gt;Na esperança de que o problema era no binário em si, já que identificar código
de inicialização do Arduino que estivesse fazendo falta parecia mais difícil, eu
decidi começar a investigar ele.&lt;/p&gt;
&lt;p&gt;Eu usei o &lt;code class="docutils literal"&gt;readelf&lt;/code&gt; para ver o conteúdo tanto do meu ELF quando do gerado pelo
Arduino e comparei eles. A maior differença no header foi essa:&lt;/p&gt;
&lt;p&gt;Meu:&lt;/p&gt;
&lt;pre class="code literal-block"&gt;
Entry point address:               0x40017d
&lt;/pre&gt;
&lt;p&gt;Arduino:&lt;/p&gt;
&lt;pre class="code literal-block"&gt;
Entry point address:               0x40376778
&lt;/pre&gt;
&lt;p&gt;O ELF do Arduino tinha muito mais código, então eu já esperava que ele fosse ter
endereços maiores, mas essa diferença era grande demais. Parecia que algum
endereço de base diferente estava sendo usado.&lt;/p&gt;
&lt;p&gt;Eu pesquisei o significado do &amp;quot;Entry point address&amp;quot; em um ELF e confirmei que
esse é o endereço de onde começa a execução. Então errar esse endereço com
certeza faria meu código nem executar.&lt;/p&gt;
&lt;p&gt;Olhando mais adiante no conteúdo do ELF:&lt;/p&gt;
&lt;p&gt;Meu:&lt;/p&gt;
&lt;pre class="code literal-block"&gt;
Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  LOAD           0x000000 0x00400000 0x00400000 0x0183c 0x0183c R E 0x1000
  LOAD           0x00183c 0x0040283c 0x0040283c 0x001a1 0x0075c RW  0x1000

 Section to Segment mapping:
  Segment Sections...
   00     .text .rodata .eh_frame
   01     .ctors .dtors .data .bss
&lt;/pre&gt;
&lt;p&gt;Arduino:&lt;/p&gt;
&lt;pre class="code literal-block"&gt;
Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  LOAD           0x001020 0x3c000020 0x3c000020 0x2a678 0x2a678 RW  0x1000
  LOAD           0x02c000 0x3fc88000 0x3fc88000 0x0c018 0x0d8a8 RW  0x1000
  LOAD           0x039000 0x40374000 0x40374000 0x0ca97 0x0ca98 RWE 0x1000
  LOAD           0x046020 0x42000020 0x42000020 0x1f347 0x1f347 R E 0x1000
  LOAD           0x000000 0x50000000 0x50000000 0x00000 0x00010 RW  0x1000

 Section to Segment mapping:
  Segment Sections...
   00     .flash_rodata_dummy .flash.appdesc .flash.rodata
   01     .dram0.dummy .dram0.data .dram0.bss
   02     .iram0.vectors .iram0.text .iram0.data
   03     .flash.text
   04     .rtc_noinit
&lt;/pre&gt;
&lt;p&gt;Depois de ler na internet sobre &lt;em&gt;program headers&lt;/em&gt; e &lt;em&gt;sections&lt;/em&gt; em um ELF, tudo
isso começou a fazer sentido.&lt;/p&gt;
&lt;p&gt;Olhando em &amp;quot;program headers&amp;quot; no ELF do Arduino, realmente há um endereço muito
próximo do &amp;quot;entry point address&amp;quot;: &lt;code class="docutils literal"&gt;0x40374000&lt;/code&gt; no segmento 2. Esse é o
endereço de base que eu estava suspeitando. O código que começa a executar então
deve estar na seção &lt;code class="docutils literal"&gt;iram0.text&lt;/code&gt;, já que seções com &lt;code class="docutils literal"&gt;text&lt;/code&gt; no nome
representam código e essa é a que está mapeada ao segmento 2. As perguntas que
vem à mente são:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Por que esse endereço é usado?&lt;/li&gt;
&lt;li&gt;Como eu configuro esse endereço no meu arquivo ELF?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Para responder a primeira pergunta, eu fui olhar no &lt;a class="reference external" href="https://www.espressif.com/sites/default/files/documentation/esp32-s3_technical_reference_manual_en.pdf"&gt;manual do ESP32-S3&lt;/a&gt;. A
figura 4-1 mostra um diagrama de como cada região de memória é mapeada. O
endereço inicial do segmento usado para o código de entrada no
Arduino, &lt;code class="docutils literal"&gt;0x40374000&lt;/code&gt;, está dentro da região &lt;code class="docutils literal"&gt;0x40370000 - 0x403dffff&lt;/code&gt; que
está mapeada à SRAM através de um barramento de instruções, o que faz todo
sentido!&lt;/p&gt;
&lt;p&gt;Mas lendo mais adiante, a tabela 4-1 subdivide as regiões de memória e dá o nome
de &amp;quot;Internal SRAM0&amp;quot; para a região contendo &lt;code class="docutils literal"&gt;0x40374000&lt;/code&gt;. Em sua descrição, é
mencionado que os primeiros 16KB do espaço podem ser reservados como cache para
instruções armazenados na memória flash. Fazendo as contas, isso significa que a
memória usável para instruções começa em... &lt;code class="docutils literal"&gt;0x40374000&lt;/code&gt;, exatamente o
endereço que foi usado para o segmento contendo código no ELF do Arduino! Então
isso explica de onde esse endereço veio.&lt;/p&gt;
&lt;p&gt;Em resumo, o problema que eu descobri foi que o arquivo ELF que eu estava
gerando tinha o código associado a endereços que não mapeiam para uma região de
memória que pode ser acessada pelo processador do ESP32 para ler instruções (ou
seja, acessível através de um barramento de instruções) e por isso não podia ser
executado.&lt;/p&gt;
&lt;p&gt;O que estava faltando era responder a segunda pergunta: Como eu configuro o
endereço certo no meu arquivo ELF?&lt;/p&gt;
&lt;p&gt;Isso era um grande lacuna no meu conhecimento, eu não fazia ideia. Mas olhando
na saída do Arduino, eu vi que o compilador estava sendo passado alguns arquivos
&lt;code class="docutils literal"&gt;.ld&lt;/code&gt; através de um parâmetro &lt;code class="docutils literal"&gt;-T&lt;/code&gt;. Dentro de um deles chamado &lt;code class="docutils literal"&gt;memory.ld&lt;/code&gt;
eu encontrei alguns endereços sendo definidos! A definição do parâmetro &lt;code class="docutils literal"&gt;-T&lt;/code&gt;
no manual do compilador revelou que esses arquivos eram &lt;em&gt;linker scripts&lt;/em&gt;, então
eu já sabia a próxima coisa que eu precisava aprender.&lt;/p&gt;
&lt;p&gt;Eu encontrei &lt;a class="reference external" href="https://allthingsembedded.com/post/2020-04-11-mastering-the-gnu-linker-script/"&gt;esse excelente artigo sobre linker scripts&lt;/a&gt; que me ensinou tudo
que eu precisava saber. Com essa informação eu consegui escrever meu &lt;em&gt;linker
script&lt;/em&gt; e associar os endereços corretos a cada seção do ELF: Não só para o
código (&lt;code class="docutils literal"&gt;.text&lt;/code&gt;), mas também para as variáveis inicializadas em zero
(&lt;code class="docutils literal"&gt;.bss&lt;/code&gt;) e outras variáveis (&lt;code class="docutils literal"&gt;.data&lt;/code&gt;), cujos endereços eu descobri do mesmo
jeito olhando o manual e o ELF do Arduino. Algumas seções adicionais também
tiveram que ser declaradas para resolver erros no linker.&lt;/p&gt;
&lt;p&gt;Como você pode ter reparado nos conteúdos dos ELF, o do Arduino tem duas seções
de código, &lt;code class="docutils literal"&gt;iram0.text&lt;/code&gt; e &lt;code class="docutils literal"&gt;flash.text&lt;/code&gt;, enquanto o meu só tem uma,
&lt;code class="docutils literal"&gt;.text&lt;/code&gt;. Por esse motivo eu inicialmente decidi apontar minha seção &lt;code class="docutils literal"&gt;.text&lt;/code&gt;
para a memória flash já que ela tem muito mais espaço. Porém o Cardputer ainda
não mostrou nenhum sinal de vida. Já que eu percebi que meu código era pequeno o
suficiente para caber inteiramente na SRAM, e o código do Arduino começava na
SRAM, eu decidi experimentar isso, e funcionou!!&lt;/p&gt;
&lt;img alt="{image}/led.jpg" src="/images/cardos-without-arduino/led.jpg" /&gt;
&lt;p&gt;A documentação do &lt;a class="reference external" href="https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-guides/startup.html#second-stage-bootloader"&gt;procedimento de inicialização da aplicação&lt;/a&gt; do ESP-IDF
descreve o que o bootloader do segundo estágio (que é o que eu ainda estou usando do
Arduino) faz e não faz, e ela menciona que é responsabilidade da aplicação
acabar de configurar a &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Memory_management_unit"&gt;MMU&lt;/a&gt; da memória flash. Esse provavelmente é o motivo pelo
qual usar o endereço da memória flash para o código não funcionou e porque o
Arduino divide o código entre uma parte na SRAM e outra na flash. Então em algum
momento eu vou precisar configurar o acesso a flash, mas por enquanto a SRAM era
o suficiente.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="codigo-de-inicializacao"&gt;
&lt;h2&gt;Código de inicialização&lt;/h2&gt;
&lt;p&gt;Agora que pelo menos o LED estava ligando, eu voltei o código para a
funcionalidade normal, ou seja, inicializar a tela, renderizar o shell e ler o
teclado.&lt;/p&gt;
&lt;p&gt;A tela começou a ser limpa como de costume mas parou no meio do caminho. Eu
removi a rotina de limpeza de tela temporariamente e o prompt do shell apareceu
na tela. Eu conseguia escrever mas depois de um curto intervalo a minha posição
voltava para a inicial. Eu percebi que o Cardputer estava sendo resetado depois
um intervalo preciso de tempo.&lt;/p&gt;
&lt;p&gt;Eu lembrei da documentação do &lt;a class="reference external" href="https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-guides/startup.html#second-stage-bootloader"&gt;procedimento de inicialização da aplicação&lt;/a&gt; que
o bootloader habilita o &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Watchdog_timer"&gt;watchdog&lt;/a&gt;. E como agora o código da aplicação era
totalmente providenciado por mim, eu tinha que cuidar disso eu mesmo: ou
continuamente resetar o watchdog ou desabilitar ele.&lt;/p&gt;
&lt;p&gt;Eu escolhi disabilitar o watchdog por simplicidade (como sempre), e depois de
ler a seção sobre watchdog do &lt;a class="reference external" href="https://www.espressif.com/sites/default/files/documentation/esp32-s3_technical_reference_manual_en.pdf"&gt;manual do ESP32-S3&lt;/a&gt; e adicionar algumas escritas
de registradores ao código, estava feito: o sistema não resetava mais. Isso
permitiu que o shell permanecesse na tela, mas ele estava cheio de lixo:&lt;/p&gt;
&lt;img alt="{image}/bss.jpg" src="/images/cardos-without-arduino/bss.jpg" /&gt;
&lt;p&gt;Mais uma vez, lembrando da documentação do &lt;a class="reference external" href="https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-guides/startup.html#second-stage-bootloader"&gt;procedimento de inicialização da
aplicação&lt;/a&gt;, o código da aplicação (eu!) é o responsável por inicializar a
seção &lt;code class="docutils literal"&gt;.bss&lt;/code&gt; para zero. Como essa seção contém as variáveis que devem ser
inicializadas em zero, se eu não inicializar ela, todas essas variáveis vão
conter lixo, que é o que eu estava vendo aqui.&lt;/p&gt;
&lt;p&gt;Eu não sabia um jeito bom de fazer isso, mas eu sabia como fazer com gambiarra:
listar manualmente todas as variáveis globais em uma função e zerar elas 🙈. E
foi isso que eu fiz e funcionou! (Nota: De lá para cá eu aprendi o jeito certo
de fazer e limpei a gambiarra &lt;a class="reference external" href="https://codeberg.org/nfraprado/cardOS/commit/a0030f679226ba4c9d296962968675d45dae2939"&gt;nesse commit&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;E com isso o SO estava finalmente funcional e sem precisar do Arduino para
compilar! E essa é a história completa por trás do commit &amp;quot;&lt;a class="reference external" href="https://codeberg.org/nfraprado/cardOS/commit/85553f797cf0877e4c88a8e549052a445d00e7d1"&gt;Move from Arduino to
generic C build flow&lt;/a&gt;&amp;quot;.&lt;/p&gt;
&lt;p&gt;Porém ainda tinha uma diferença de antes da mudança, ele estava muito mais
lento. Isso é porque, pela última vez olhando na página de &lt;a class="reference external" href="https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-guides/startup.html#second-stage-bootloader"&gt;procedimento de inicialização da
aplicação&lt;/a&gt;, a aplicação que deve configurar a frequência do clock da CPU para o
valor desejado. A frequência padrão é bem lenta, mas o código de inicialização
implicitamente adicionado pelo Arduino configurava ela para um valor mais alto.
Eu consertei isso em &lt;a class="reference external" href="https://codeberg.org/nfraprado/cardOS/commit/2034cf7d5223fd242e8827a169218559366e9496"&gt;um commit subsequente&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="outras-melhorias"&gt;
&lt;h2&gt;Outras melhorias&lt;/h2&gt;
&lt;p&gt;Com a remoção do Arduino feita, eu finalmente pude fazer algumas melhorias que
eu queria há muito tempo.&lt;/p&gt;
&lt;p&gt;A primeira coisa que eu fiz foi separar o código-fonte que era em um único
arquivo em vários arquivos diferentes &lt;a class="reference external" href="https://codeberg.org/nfraprado/cardOS/commit/6c8dc5baa9ed1447b1e5083109b742f6eda68f32"&gt;nesse commit&lt;/a&gt;. Eu estava muito ansioso
para essa mudança e me senti muito bem em fazê-la 😌. Agora o código está muito
mais organizado, é mais fácil encontrar as coisas e focar em um único
componente.&lt;/p&gt;
&lt;p&gt;Eu também habilitei todos os principais warnings do compilador &lt;a class="reference external" href="https://codeberg.org/nfraprado/cardOS/commit/f2dfd26dd05748ebef2312aedb8d88aeeda607b7"&gt;nesse commit&lt;/a&gt;,
incluindo um warning sobre fallthrough implícito em &lt;em&gt;case switches&lt;/em&gt; que teria me
salvo alguns minutos investigando um bug no começo desse projeto.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusao"&gt;
&lt;h2&gt;Conclusão&lt;/h2&gt;
&lt;p&gt;Esse foi mais um grande passo para o projeto. Eu me diverti bastante e aprendi
muito.&lt;/p&gt;
&lt;p&gt;O próximo grande passo é implementar leitura e escrita no cartão SD e um sistema
de arquivos. Provavelmente vai levar um bom tempo até eu acabar isso e voltar
com uma atualização no blog. Fique à vontade para checar o &lt;a class="reference external" href="https://codeberg.org/nfraprado/cardOS"&gt;repositório&lt;/a&gt; para
acompanhar as últimas atualizações no meio tempo se tiver curiosidade!&lt;/p&gt;
&lt;/div&gt;
</content><category term="2024"/><category term="cardputer"/><category term="esp32"/><category term="so"/></entry><entry><title>CardOS: Escrevendo um SO para o Cardputer</title><link href="https://nfraprado.net/pt-br/post/cardos-escrevendo-um-so-para-o-cardputer.html" rel="alternate"/><published>2024-05-25T00:00:00-03:00</published><updated>2024-05-25T00:00:00-03:00</updated><author><name>Nícolas F. R. A. Prado</name></author><id>tag:nfraprado.net,2024-05-25:/pt-br/post/cardos-escrevendo-um-so-para-o-cardputer.html</id><summary type="html">&lt;p&gt;Recentemente eu comprei um &lt;a class="reference external" href="https://docs.m5stack.com/en/core/Cardputer"&gt;Cardputer da M5Stack&lt;/a&gt;. O que me motivou foi que eu
conhecia outras pessoas pessoalmente que também tinham ele, então eu teria com
quem compartilhar o meu progresso, mas eu estava preocupado em acabar sendo só
mais uma placa que ficaria guardada no meu armário para sempre …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Recentemente eu comprei um &lt;a class="reference external" href="https://docs.m5stack.com/en/core/Cardputer"&gt;Cardputer da M5Stack&lt;/a&gt;. O que me motivou foi que eu
conhecia outras pessoas pessoalmente que também tinham ele, então eu teria com
quem compartilhar o meu progresso, mas eu estava preocupado em acabar sendo só
mais uma placa que ficaria guardada no meu armário para sempre sem ser tocada.
De qualquer forma eu resolvi arriscar.&lt;/p&gt;
&lt;p&gt;Assim que eu peguei ele nas mãos, ele me conquistou. Ele tem tudo que é
necessário para ser um computador: teclado completo para entrada, tela para
saída, bateria para portabilidade, entrada de cartão microSD para armazenamento
persistente, além de WiFi e Bluetooth para conectividade. O fato de ele ser um
computador muito limitado, e tão minúsculo, só fez eu achar ele mais charmoso.&lt;/p&gt;
&lt;p&gt;Eu tenho muito interesse na idea de implementar um sistema completo do zero para
entender todas as diferentes camadas e interações. Eu também sempre quis fazer o
meu próprio sistema operacional (SO). Então antes de eu perceber, eu já tinha
decidido em um objetivo: Eu iria fazer meu próprio SO do zero para o Cardputer.&lt;/p&gt;
&lt;p&gt;Mas quando eu digo do zero, eu não quero dizer necessariamente começar com um
arquivo vazio, mas que eventualmente todo código tem que ter sido escrito por
mim. Uma coisa que eu lembro dos vídeos do &lt;a class="reference external" href="https://www.youtube.com/c/AndreasKling"&gt;Andreas Kling&lt;/a&gt; (criador do
SerenityOS) é que ele não começou a escrever o SO dele pelo bootloader, mas pela
interface de usuário, e que isso o permitiu ver imediatamente o efeito das
mudanças que ele fazia e trabalhar em pequenos passos em direção ao SO completo.
Eu também lembro do &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Tarn_Adams"&gt;Tarn Adams&lt;/a&gt; (co-criador do Dwarf Fortress) dando a mesma
dica sobre como manter motivação no desenvolvimento de um jogo. E ambas são
pessoas que eu admiro profundamente, então eu segui o conselho deles.&lt;/p&gt;
&lt;p&gt;Com isso em mente, eu comecei meu projeto a partir de &lt;a class="reference external" href="https://github.com/m5stack/M5Cardputer/blob/master/examples/Basic/keyboard/inputText/inputText.ino"&gt;uma demo do Cardputer&lt;/a&gt;
que já tinha tela e teclado funcionais, e &lt;a class="reference external" href="https://codeberg.org/nfraprado/cardOS/commit/8b1e75cfb9064f27d641296f06ff0fa89ac2dadc"&gt;eu escrevi um shell simples em cima
dela&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Só depois disso que eu comecei a implementar meu próprio suporte ao teclado e
à tela do zero e remover o código da demo.&lt;/p&gt;
&lt;div class="section" id="fazendo-a-tela-funcionar"&gt;
&lt;h2&gt;Fazendo a tela funcionar&lt;/h2&gt;
&lt;p&gt;Implementar suporte para a tela foi muito mais complicado do que eu esperava e
eu fiquei travado por um tempo. Nessa etapa eu já tinha implementado meu próprio
suporte para o teclado, testado ele e ele tinha funcionado. Mas eu não conseguia
fazer a tela nem mesmo acender, mesmo depois de ter lido o datasheet dela com
cuidado várias vezes e implementado todo o procedimento de inicialização.&lt;/p&gt;
&lt;p&gt;Eventualmente eu dei um passo para trás e testei funcionalidades mais básicas e
percebi que meu código para configurar GPIOs como saída não estava funcionando.
Isso foi uma surpresa porque isso era necessário para o teclado, e eu já tinha
testado ele. Mas o que eu não tinha percebido é que apesar de o meu código ser
capaz de ligar e desligar pinos de saída para escanear o teclado, ele estava
dependendo implicitamente do código de inicialização da demo para que os pinos
de saída funcionassem, e por isso quando eu troquei todo esse código pela minha
própria implementação da tela nem a tela nem o teclado funcionavam.&lt;/p&gt;
&lt;p&gt;Mas como muitas vezes é o caso em comportamentos inexplicáveis, esse não era o
único problema. Para minha surpresa, o manual do ESP32 apresenta o endereço
errado para um dos registradores necessários para configurar GPIOs. Por sorte
ele também mostra o endereço correto em outra página, e eventualmente eu percebi
essa diferença.&lt;/p&gt;
&lt;p&gt;Então depois que eu implementei o suporte para &lt;a class="reference external" href="https://codeberg.org/nfraprado/cardOS/commit/5dda03f7af0d61af3ad2d81191a04d38ab7cc790"&gt;habilitar GPIOs de saída&lt;/a&gt; e
&lt;a class="reference external" href="https://codeberg.org/nfraprado/cardOS/commit/0c380d6e9bb28816c5d55074d52bfca961696a2e"&gt;corrigi o endereço do registrador&lt;/a&gt;, eu consegui &lt;a class="reference external" href="https://codeberg.org/nfraprado/cardOS/commit/3095638dba5e6d40be7b592cfa52fb3835f35a83"&gt;consertar o suporte do
teclado para ser completamente auto-contido&lt;/a&gt;, e consegui finalmente, depois de
muito suor, &lt;a class="reference external" href="https://codeberg.org/nfraprado/cardOS/commit/4706bea4bbdbc234c0054cd09c26a91a50865b5c"&gt;implementar o suporte para a tela&lt;/a&gt; e remover a dependência a
bibliotecas externas.&lt;/p&gt;
&lt;p&gt;Essa foi a maior conquista do projeto até agora e eu estou muito orgulhoso!&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="estado-atual"&gt;
&lt;h2&gt;Estado atual&lt;/h2&gt;
&lt;p&gt;Esse é o estado atual do cardOS:&lt;/p&gt;
&lt;object data="/images/cardos/cardos_demo.mp4" type="video/mp4"&gt;{image}/cardos_demo.mp4&lt;/object&gt;
&lt;p&gt;Em outras palavras, ele tem suporte a teclado e tela sem depender de bibliotecas
externas. A comunicação com a tela é bem lenta, então tem bastante oportunidade
para melhorias futuras 🙂. Ele tem um shell com alguns comandos, incluindo
&lt;code class="docutils literal"&gt;help&lt;/code&gt; para mostrar os comandos, &lt;code class="docutils literal"&gt;clear&lt;/code&gt; para limpar a tela, &lt;code class="docutils literal"&gt;read&lt;/code&gt; para
ler um endereço de memória arbitrário e &lt;code class="docutils literal"&gt;led&lt;/code&gt; para mudar a cor do LED RGB
integrado. É possível navegar pelo histórico de comandos do shell e repetir
comandos anteriores. E quando o terminal enche a tela ela é limpa
automaticamente.&lt;/p&gt;
&lt;p&gt;Eu também passei um tempo consertando problemas. Nesse ponto o SO pareceu
finalmente estar usável, então &lt;a class="reference external" href="https://codeberg.org/nfraprado/cardOS/releases/tag/v0.1"&gt;eu marquei ele&lt;/a&gt; &lt;code class="docutils literal"&gt;v0.1&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="proximos-passos"&gt;
&lt;h2&gt;Próximos passos&lt;/h2&gt;
&lt;p&gt;A próxima coisa em que eu estou trabalhando é em parar de usar a toolchain do
Arduino. O principal motivo é que ela adiciona código implicitamente ao binário
quando usada, e eu quero continuar removendo dependências do meu código.&lt;/p&gt;
&lt;p&gt;Além disso, o Arduino basicamente força o uso de um único arquivo de código
fonte. O fonte do cardOS é atualmente um único arquivo de 1900 linhas (sendo que
600 são &lt;a class="reference external" href="https://codeberg.org/nfraprado/cardOS/src/commit/de836cdfa14bc63822665833ba27c57002b74f39/cardOS.ino#L1122"&gt;a estrutura contendo os caracteres da fonte&lt;/a&gt;) e eu estou começando a
me perder nele, então é uma boa hora para separar o código em múltiplos
arquivos.&lt;/p&gt;
&lt;p&gt;Finalmente, o Arduino usa C++, e já que eu estou programando em C, essa mudança
me permitiria usar um compilador de C, que fornece mensagens de erro melhores.&lt;/p&gt;
&lt;p&gt;Depois disso eu vou continuar trabalhando para fazer o cardOS ser um SO de
verdade. Eu quero que ele passe a sensação de um computador hackeável, então
minha meta é que seja possível abrir um editor de texto, editar um arquivo de
inicialização de shell, salvar, reiniciar e ver os efeitos dessa mudança. Isso
já me da trabalho suficiente: Eu preciso criar um editor de texto, uma linguagem
de script de shell, e implementar suporte ao leitor de cartão microSD e a um
sistema de arquivos.&lt;/p&gt;
&lt;p&gt;Mais no futuro eu estou bastante curioso sobre implementar suporte ao WiFi e
quem sabe ser possível mandar e receber mensagens de IRC, se for possível. Tenho
certeza que vai ser difícil, mas também vai ser divertido 😃.&lt;/p&gt;
&lt;p&gt;E é sobre isso: diversão! Fazem dois meses desde que eu comecei o projeto e eu
posso dizer confiança que esse é o projeto em que eu mais me diverti de todos.
Então enquanto ele continuar divertido eu vou continuar mexendo nele. Mas sem
pressa para acabar, eu estou aproveitando a viagem 🙂.&lt;/p&gt;
&lt;/div&gt;
</content><category term="2024"/><category term="cardputer"/><category term="esp32"/><category term="so"/></entry><entry><title>vCard + RSS como alternativa às redes sociais</title><link href="https://nfraprado.net/pt-br/post/vcard-rss-como-alternativa-as-redes-sociais.html" rel="alternate"/><published>2024-03-22T00:00:00-03:00</published><updated>2024-03-22T00:00:00-03:00</updated><author><name>Nícolas F. R. A. Prado</name></author><id>tag:nfraprado.net,2024-03-22:/pt-br/post/vcard-rss-como-alternativa-as-redes-sociais.html</id><summary type="html">&lt;p&gt;Ano passado depois de conversar um pouco com alguém durante uma conferência, ele
perguntou o meu LinkedIn para manter contato, e eu respondi que não tenho.&lt;/p&gt;
&lt;p&gt;Já fazem vários anos desde que eu decidi sair das redes sociais. Eu não sinto
falta. Aplicativos de mensagem instantânea me permitem manter contato …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Ano passado depois de conversar um pouco com alguém durante uma conferência, ele
perguntou o meu LinkedIn para manter contato, e eu respondi que não tenho.&lt;/p&gt;
&lt;p&gt;Já fazem vários anos desde que eu decidi sair das redes sociais. Eu não sinto
falta. Aplicativos de mensagem instantânea me permitem manter contato com as
pessoas que realmente são importantes para mim em um nível muito mais pessoal.&lt;/p&gt;
&lt;p&gt;Ainda assim, essa interação ficou na minha cabeça. De fato seria legal ter um
registro dessas conexões também. As pessoas com quem você teve uma conversa
interessante por alguns minutos. Poder checar como elas estão de vez em quando,
onde estão trabalhando e no que estão hackeando.&lt;/p&gt;
&lt;p&gt;Basicamente eu gostaria que todo mundo tivesse um perfil online e um feed, que
eu pudesse facilmente checar e seguir, e que os dados associados a eu estar
seguindo pudessem ser armazenados localmente. Por que isso precisa ser exclusivo
a redes sociais?&lt;/p&gt;
&lt;p&gt;Não é. Na verdade, a parte do feed já foi resolvido há muito tempo com o
&lt;a class="reference external" href="https://en.wikipedia.org/wiki/RSS"&gt;RSS&lt;/a&gt;/&lt;a class="reference external" href="https://en.wikipedia.org/wiki/Atom_(web_standard)"&gt;Atom&lt;/a&gt;. Então só é necessário ter a URL do feed RSS/Atom de alguém, que você
pode adicioná-la ao seu leitor de feed e facilmente seguir as atualizações de
uma pessoa independente de onde elas são publicadas.&lt;/p&gt;
&lt;p&gt;A parte do perfil, um lugar com pontos chave sobre o estado atual de uma pessoa
(nome, foto, localizaçäo, empresa, etc) é a que historicamente foi mais atrelada
a redes sociais. Sites pessoais muitas vezes tem uma página &amp;quot;sobre&amp;quot; com essas
informações, mas por conta da falta de estrutura eu não acho elas tão úteis
quanto os perfis de redes sociais.&lt;/p&gt;
&lt;p&gt;Mas na verdade existe um padrão aberto amplamente usado para armazenar perfis
também: &lt;a class="reference external" href="https://en.wikipedia.org/wiki/VCard"&gt;vCard&lt;/a&gt;. Ele é conhecido princpalmente como o formato usado por clientes
de email para armazenar contatos, os quais são até sincronizados entre servidor
e clientes usando o protocolo &lt;a class="reference external" href="https://en.wikipedia.org/wiki/CardDAV"&gt;CardDAV&lt;/a&gt;. Mas vCards também são usados para
contatos no celular: é possível importar e exportar contatos no iOS e Android
usando vCards.&lt;/p&gt;
&lt;p&gt;Então foi aí que eu percebi, se existe um padrão aberto amplamente usado para
feeds, e outro para perfis, por que não juntar os dois? Ao colocar a URL de um
feed RSS/Atom em um vCard, ele se torna um único arquivo com toda a informação
necessária para identificar uma pessoa e acompanhar as atualizações dela. Por
ser um único arquivo, é fácil de compartilhar com os outros e também de
armazenar os vCards de todas as suas conexões localmente.&lt;/p&gt;
&lt;p&gt;Agora, um ponto interessante é que apesar da informação de perfil em um
vCard poder ficar desatualizada, uma das propriedades disponíveis é a &lt;code class="docutils literal"&gt;SOURCE&lt;/code&gt;
que tem como objetivo armazenar a URL de onde a última versão do vCard pode ser
obtida. Ou seja, ao hospedar seu vCard em uma URL pública, por exemplo no seu
site pessoal, é possível que pessoas que já baixaram ele o mantenham atualizado.&lt;/p&gt;
&lt;p&gt;Quanto a como a URL do feed pode ser incluída no vCard, depois de ler por cima a
&lt;a class="reference external" href="https://datatracker.ietf.org/doc/html/rfc6350"&gt;última versão da especificação&lt;/a&gt;, eu originalmente tinha a intenção de que ela
fosse uma propriedade &lt;code class="docutils literal"&gt;URL&lt;/code&gt; padrão. Mas eu queria que a URL principal do vCard
ainda fosse a do site da pessoa, então algum mecanismo seria necessário para
diferenciar entre as duas. Minha primeira ideia foi usar o parâmetro
&lt;code class="docutils literal"&gt;MEDIATYPE&lt;/code&gt; na &lt;code class="docutils literal"&gt;URL&lt;/code&gt;, mas a especificação diz explicitamente que para
protocolos como o HTTP o cabeçalho HTML &lt;code class="docutils literal"&gt;Content-type&lt;/code&gt; deveria ser usado para
isso. Porém, aparentemente, apesar de feeds RSS/Atom terem &lt;em&gt;media type&lt;/em&gt; próprios
padronizados (&lt;code class="docutils literal"&gt;application/rss+xml&lt;/code&gt; e &lt;code class="docutils literal"&gt;application/atom+xml&lt;/code&gt;
respectivamente), na prática, &lt;code class="docutils literal"&gt;text/xml&lt;/code&gt; é usado, o que não os identifica como
feed. Então no fim, para conseguir determinar que uma URL em um vCard é de um
feed, a URL teria que ser baixada e o arquivo lido. Para evitar essa
complicação, eu decidi, relutantemente, usar uma propriedade não padronizada,
&lt;code class="docutils literal"&gt;X-FEED&lt;/code&gt;, para o feed.&lt;/p&gt;
&lt;div class="section" id="testando-na-pratica"&gt;
&lt;h2&gt;Testando na prática&lt;/h2&gt;
&lt;p&gt;Para conseguir testar a ideia na prática, eu criei &lt;a class="reference external" href="https://codeberg.org/nfraprado/vcard-network"&gt;esse repositório&lt;/a&gt; com dois
scripts simples. O primeiro é o script &lt;code class="docutils literal"&gt;vcard-render&lt;/code&gt; que lê vCards e
renderiza eles usando um template &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Jinja_(template_engine)"&gt;jinja&lt;/a&gt;. A ideia é que você pode usá-lo para
renderizar o seu vCard e o vCard de suas conexões no seu site, que é exatamente
como eu estou usando ele &lt;a class="reference external" href="https://nfraprado.net/br/pages/vcard-network.html"&gt;nesta página que eu adicionei ao meu site&lt;/a&gt; (ela pode
ser encontrada através da página Sobre). Para detalhes sobre como eu integrei o
script no meu website, veja &lt;a class="reference external" href="https://codeberg.org/nfraprado/blog/commit/148c9cf31eb10434ba127e94332e51397f18db99"&gt;este commit&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;O outro script é o &lt;code class="docutils literal"&gt;vcard-to-opml&lt;/code&gt; que extrai feeds de vCards e gera um
arquivo OPML com eles, o qual pode então ser importado em um leitor de feeds. A
ideia é usar esse script nos vCards de todas as pessoas que você baixou (suas
conexões) para que você possa facilmente seguir os feeds delas. Eu uso o leitor
de feeds &lt;a class="reference external" href="https://newsboat.org/"&gt;newsboat&lt;/a&gt;, então eu adicionaria o seguinte comando em um crontab para
rodá-lo periodicamente (assim que eu tiver algumas conexões de verdade!):&lt;/p&gt;
&lt;pre class="code literal-block"&gt;
newsboat -i &amp;lt;(awk 1 ~/ark/etc/dav/contacts/5DEB-65D21680-2F1-4AEA3480/* | ./vcard-to-opml.py)
&lt;/pre&gt;
&lt;p&gt;Nota: &lt;code class="docutils literal"&gt;awk&lt;/code&gt; é usado aqui ao invés de um simples &lt;code class="docutils literal"&gt;cat&lt;/code&gt; porque aparentemente
esses vCards não têm &lt;code class="docutils literal"&gt;\n&lt;/code&gt; no final do arquivo. Não sei se isso é culpa do meu
provedor de email, onde eu guardo meus vCards, ou do &lt;a class="reference external" href="http://vdirsyncer.pimutils.org/en/stable/"&gt;vdirsyncer&lt;/a&gt;, que é o
programa que eu uso para manter uma cópia local dos meus vCards.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusao"&gt;
&lt;h2&gt;Conclusão&lt;/h2&gt;
&lt;p&gt;Se você tiver interesse em experimentar usar vCards e RSS/Atom para conectar com
pessoas também, dê uma olhada no &lt;a class="reference external" href="https://codeberg.org/nfraprado/vcard-network"&gt;repositório&lt;/a&gt;, que contém esses scripts. E
fique à vontade para entrar em contato para me dizer que está usando, você pode
achar meu email no... claro, no meu vCard 😉.&lt;/p&gt;
&lt;p&gt;Mesmo se ninguém usar isso, pelo menos agora eu tenho uma boa resposta para a
próxima pessoa que pedir meu LinkedIn. &amp;quot;Escaneie esse QR code e você vai ver meu
perfil&amp;quot; 🙂.&lt;/p&gt;
&lt;img alt="{image}/qrcode_br.png" src="/images/vcard-network/qrcode_br.png" /&gt;
&lt;/div&gt;
</content><category term="2024"/><category term="vcard"/><category term="rss"/><category term="atom"/><category term="redes sociais"/></entry><entry><title>Colecionando pins</title><link href="https://nfraprado.net/pt-br/post/colecionando-pins.html" rel="alternate"/><published>2023-11-23T00:00:00-03:00</published><updated>2023-11-23T00:00:00-03:00</updated><author><name>Nícolas F. R. A. Prado</name></author><id>tag:nfraprado.net,2023-11-23:/pt-br/post/colecionando-pins.html</id><summary type="html">&lt;p&gt;Começou inocentemente mais ou menos um ano atrás quando eu estava procurando um
presente para um amigo. A gente jogou muito League of Legends e Ragnarok Online
nos tempos de escola, então eu queria dar uma recordação disso para ele.
Eventualmente eu encontrei esses pins:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.etsy.com/listing/587004849/poring-poporing-slime-monter-fanart-hard"&gt;Poring/Poporing do Ragnarok Online …&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;</summary><content type="html">&lt;p&gt;Começou inocentemente mais ou menos um ano atrás quando eu estava procurando um
presente para um amigo. A gente jogou muito League of Legends e Ragnarok Online
nos tempos de escola, então eu queria dar uma recordação disso para ele.
Eventualmente eu encontrei esses pins:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.etsy.com/listing/587004849/poring-poporing-slime-monter-fanart-hard"&gt;Poring/Poporing do Ragnarok Online&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.etsy.com/listing/1280961309/arcane-enamel-pin-fanmade-vis-hextech"&gt;Luva da Vi do League of Legends&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Eles eram exatamente o que eu estava procurando então eu comprei. Era para
acabar aí, mas eu ainda sou muito fã do Ragnarok Online, e esses pins eram tão
fofos que eu pensei, eu deveria comprar para mim também!&lt;/p&gt;
&lt;p&gt;Mas depois disso eu não conseguia mais parar de procurar por pins. Eu estava
viciado! Eu continuei navegando no Etsy, pesquisando pins de jogos que eu gosto,
e favoritando eles para que eu conseguisse encontrá-los depois.&lt;/p&gt;
&lt;p&gt;Eu também comecei a procurar pins em toda loja de souvenirs, que nunca tinham me
chamado atenção, que eu passava. Agora eu realmente tinha algo de interessante
para comprar! Então depois de algumas semanas eu já estava com uma pequena
coleção:&lt;/p&gt;
&lt;img alt="{image}/pins-before-pax.jpg" src="/images/pins/pins-before-pax.jpg" /&gt;
&lt;p&gt;Nessa época eu estava pensando em ir para a &lt;a class="reference external" href="https://en.wikipedia.org/wiki/PAX_(event)"&gt;PAX east&lt;/a&gt;. Quando eu descobri que
o evento atraia uma grande comunidade de colecionadores de pin e contava com
eventos de troca de pins, através &lt;a class="reference external" href="https://www.youtube.com/watch?v=7hoSSI6A-_U"&gt;desse vídeo&lt;/a&gt;, eu percebi que eu tinha que
ir.&lt;/p&gt;
&lt;p&gt;Para conseguir levar meus pins convenientemente para a PAX, e trazer de volta os
que eu conseguisse lá, eu comprei um &lt;a class="reference external" href="https://www.amazon.com/dp/B07YYR3J1V/ref=cm_sw_r_as_gl_api_gl_i_NJTTQQ2YXTGC585NB7YN"&gt;PinFolio Classic&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;No meu primeiro dia na PAX eu não me segurei e comprei todos os pins que eu
gostei:&lt;/p&gt;
&lt;img alt="{image}/pins-bought-1st-day-pax.jpg" src="/images/pins/pins-bought-1st-day-pax.jpg" /&gt;
&lt;p&gt;Depois de ter muita sorte e ganhar dois pins de fita cassete dourada em seguida
de uma roleta, eu fui para a sala de troca de pins para trocar alguns pins e
conhecer pessoas novas. Foi divertido reconhecer algumas das mesmas pessoas que
apareceram no vídeo.&lt;/p&gt;
&lt;p&gt;Ao final da PAX, essa era minha coleção:&lt;/p&gt;
&lt;img alt="{image}/pinfolio-after-pax.jpg" src="/images/pins/pinfolio-after-pax.jpg" /&gt;
&lt;p&gt;(o pin do Deviruchi está faltando porque estava preso na minha touca)&lt;/p&gt;
&lt;p&gt;Agora, muitos meses depois eu finalmente completei uma caixa com a minha
coleção. Eu tenho mais alguns outrs pins, mas a minha intenção é usá-los para
trocas em eventos futuros, então eles estão guardados no pinfolio. Então sem
mais delongas, aqui está minha coleção, numerada:&lt;/p&gt;
&lt;img alt="{image}/pins-full-labeled.jpg" src="/images/pins/pins-full-labeled.jpg" /&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Poring. Do Ragnarok Online. Feito por fã (Teaberryhouse). O primeiro da coleção! &lt;a class="reference external" href="https://www.etsy.com/listing/587004849/poring-poporing-slime-monter-fanart-hard"&gt;Link&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Poporing. Veja 1.&lt;/li&gt;
&lt;li&gt;&lt;code class="docutils literal"&gt;/gg&lt;/code&gt; Emote. Veio com 4.&lt;/li&gt;
&lt;li&gt;Deviruchi. Do Ragnarok Online. Feito por fã (FinniChang). Comprado junto com
1 e 2. &lt;a class="reference external" href="https://www.etsy.com/listing/742804941/deviruchi-gg-ragnarok-online-enamel-pin"&gt;Link&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Eevee. Do Pokemon. Feito por fã (Jackalandhare). Eu estava comprando dois
pins para um amigo que ama Pokemon e o terceiro era mais barato, então eu
comprei um para mim também. &lt;a class="reference external" href="https://www.etsy.com/listing/729815460/hard-enamel-pins-eeveelution-eevee?variation0=1222773687"&gt;Link&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Fairy bottle. Do Zelda. Feito por fã (FinniChang). &lt;a class="reference external" href="https://www.etsy.com/listing/842366471/fairy-bottle-enamel-pin?variation0=1473891219"&gt;Link&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;A Noite Estrelada de Van Gogh. Comprei enquanto visitava o Museu de Arte
Moderna em NYC, onde eles mantêm o quadro real.&lt;/li&gt;
&lt;li&gt;Príncipe e Katamari. Do Katamari Damacy. Eu joguei Katamari Damacy Reroll ano
passado e achei muito legal! E quem consegue resistir a um pin que gira?
Comprado no stand da Fangamer na PAX. &lt;a class="reference external" href="https://www.fangamer.com/collections/katamari-damacy/products/katamari-damacy-the-prince-katamari-spinning-pin"&gt;Link&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Rei. Veja 8. &lt;a class="reference external" href="https://www.fangamer.com/collections/katamari-damacy/products/king-of-all-cosmos-pin-spinning"&gt;Link&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Corvinal. Do Harry Potter. Eu comprei em uma loja do Harry Potter no
aeroporto de Londres, onde eu tinha uma conexão. Eu não ligo para Corvinal,
mas era o pin mais bonito da loja.&lt;/li&gt;
&lt;li&gt;Junimos. Do Stardew Valley. Comprado no stand da Fangamer na PAX. &lt;a class="reference external" href="https://www.fangamer.com/collections/stardew-valley/products/pinverse-junimos-pin-pack"&gt;Link&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Veio com 11.&lt;/li&gt;
&lt;li&gt;Babaa (Esse é o nome aparentemente!). Do Neopets. Eu joguei muito Neopets
quando eu era mais novo, então quando vi esses pins disponíveis no stand da
Geekify na PAX, eu tive que comprar.&lt;/li&gt;
&lt;li&gt;Pincel de Morango e Chocolate. Veja 13.&lt;/li&gt;
&lt;li&gt;Pincel de Espectro. Veja 13.&lt;/li&gt;
&lt;li&gt;Botão Fight. Do Undertale. Eu sou muito fã do Undertale como deve ter ficado
óbvio pelas fotos anteriores. Comprado no stand da Fangamer na PAX. &lt;a class="reference external" href="https://www.fangamer.com/collections/undertale/products/undertale-mercy-or-fight-enamel-pin-set"&gt;Link&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Botão Mercy. Veio com 16.&lt;/li&gt;
&lt;li&gt;Annoying dog. Veja 16. &lt;a class="reference external" href="https://www.fangamer.com/collections/undertale/products/undertale-annoying-dog-lapel-pin"&gt;Link&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Máquina de Fliperama do Wonderville. Comprei enquanto visitava o
&lt;a class="reference external" href="https://www.wonderville.nyc/"&gt;Wonderville&lt;/a&gt;, que é um lugar único e incrível, com um amigo.&lt;/li&gt;
&lt;li&gt;Psychokinetic Raz. Do Psychonauts 2. Eu ainda não joguei o jogo, só o
primeiro, mas eu tinha acabado de terminar de assistir o &lt;a class="reference external" href="https://www.youtube.com/watch?v=kRlI72bsNRc&amp;amp;list=PLIhLvue17Sd70y34zh2erWWpMyOnh4UN_"&gt;documentário
PsychOdyssey&lt;/a&gt; que é incrível e então comprei esse pin como recordação.
Comprado no stand da Fangamer na PAX. &lt;a class="reference external" href="https://www.fangamer.com/products/raz-psychonauts-pin-spinning"&gt;Link&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Fantasma da Ópera. Comprei na saída do espetáculo do Fantasma da Ópera na
Broadway. Foi muito bom!&lt;/li&gt;
&lt;li&gt;Canada Wilderness. Ganhei de um amigo. Ele comprou durante uma viagem nossa
para Toronto (acho que na loja da torre).&lt;/li&gt;
&lt;li&gt;Loto. Ganhei de um amigo. É um pin antigo, diferente de todos os outros
nessa coleção. Eu acho legal que ele tem números para cada um dos minúsculos
quadradinhos.&lt;/li&gt;
&lt;li&gt;Frisk. Do Undertale. Comprado no stand da Fangamer na PAX. &lt;a class="reference external" href="https://www.fangamer.com/collections/undertale/products/undertale-character-pins-set-1"&gt;Link&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Toriel. Veio com 24.&lt;/li&gt;
&lt;li&gt;Sans. Veio com 24.&lt;/li&gt;
&lt;li&gt;Flowey. Veio com 24.&lt;/li&gt;
&lt;li&gt;Papyrus. Veja 24. &lt;a class="reference external" href="https://www.fangamer.com/collections/undertale/products/undertale-character-pins-set-2"&gt;Link&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Undyne. Veio com 28.&lt;/li&gt;
&lt;li&gt;Alphys. Veio com 28.&lt;/li&gt;
&lt;li&gt;Mettaton. Veio com 28.&lt;/li&gt;
&lt;li&gt;Máscara. Do Mr Robot. Comprei usado da internet. É de uma Loot Crate de
2016. Queria muito que ele fosse menor.&lt;/li&gt;
&lt;li&gt;Logo da Software Freedom Conservancy. Ganhei de um amigo.&lt;/li&gt;
&lt;li&gt;Logo do L. Do Death Note. Comprei em uma loja geek em Antwerp.&lt;/li&gt;
&lt;li&gt;Anão. Do Dwarf Fortress. Um dos motivos de eu ter ido para a PAX foi para
conhecer os criadores do jogo pessoalmente (e eu conheci!). Logo depois um
pin de Dwarf Fortress foi anunciado e eu comprei online no mesmo dia. &lt;a class="reference external" href="https://www.fangamer.com/products/dwarf-fortress-pin-finely-crafted"&gt;Link&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;VHS PAX East. Comprado na PAX. As pessoas na PAX praticamente só trocam
pins que sejam Pinny Arcade, então eu comprei esse como parte de um conjunto
de 4 pins para começar minha coleção e poder trocar no evento.&lt;/li&gt;
&lt;li&gt;Logo da PAX East 2023. Veio com 36.&lt;/li&gt;
&lt;li&gt;Bloco de Minério de Diamante. Do Minecraft. Consegui esse trocando na PAX.
Minecraft foi um dos jogos com maior impacto na minha vida, então eu fiquei
muito feliz de conseguir ele.&lt;/li&gt;
&lt;li&gt;ATLAS e P-Body. Do Portal 2. Consegui ele trocando na PAX. Eu amo Portal,
mas esse é o pin que eu menos gostei do &lt;a class="reference external" href="https://pinnydb.com/pinDetail/271"&gt;set&lt;/a&gt;, então eu quero tentar trocar ele por
um dos outros na próxima PAX.&lt;/li&gt;
&lt;li&gt;Raz. Do Psychonauts. Eu ganhei esse de graça de uma pessoa na sala de trocas
da PAX. Ela disse que já tinha muitos pins e já que eu estava começando
minha coleção e gostei dele, eu podia ficar com ele 🙂.&lt;/li&gt;
&lt;li&gt;Bloco ?. Do Super Mario Bros. Comprado na PAX como parte de um conjunto de 4
para começar minha coleção Pinny Arcade e conseguir trocar. &lt;a class="reference external" href="https://store.penny-arcade.com/collections/pins/products/super-mario-bros-1-1-pin-set"&gt;Link&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Mario. Veio com 41.&lt;/li&gt;
&lt;li&gt;Padrinhos mágicos. Eu assisti muuitas horas desse desenho no Jetix quando
era criança. Até onde eu sei o único pin oficial, feito pelo Zen Monkey
Studios. &lt;a class="reference external" href="https://www.ebay.com/itm/234773032756"&gt;Link&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Trem de Paranaguá. Presente de um amigo que andou nesse trem. Agora eu
preciso ir lá um dia usando esse pin 😃.&lt;/li&gt;
&lt;li&gt;Kerbal Space Program. Ganhei da mesma pessoa que 40.&lt;/li&gt;
&lt;li&gt;Green Flame. Da Acquisitions Inc. É uma campanha de RPG de mesa jogada ao
vivo durante a PAX. Eu assisti na PAX e comprei esse pin de recordação. &lt;a class="reference external" href="https://store.penny-arcade.com/collections/pins/products/green-flame-pin"&gt;Link&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Mae. Do Night in the Woods. Eu comprei ele na PAX antes de ter jogado o
jogo. Eu simplesmente amei o estilo artístico. Provavelmente meu pin
preferido.&lt;/li&gt;
&lt;li&gt;Los Andes. Ganhei de um amigo que visitou esse lugar, e ele adora escalar,
então achei bem apropriado.&lt;/li&gt;
&lt;li&gt;Ganso. Do Kickstarter. Durante a PAX, o Kickstarter espalhou placas de
ganso pelos stands de cada projeto financiado pelo Kickstarter. Se você
encontrasse todos você ganhava um pin. Foi muito difícil mas eu consegui.&lt;/li&gt;
&lt;li&gt;Fita de ouro da PAX. Durante a PAX você podia pagar pins Pinny Arcade para
girar uma roleta e ganhar a fita que caisse. Eu fui extremamente sortudo e
consegui duas fitas de ouro seguidas. Eu troquei uma e fiquei com a outra.&lt;/li&gt;
&lt;li&gt;Azulejo. Comprei durante uma viagem para Portugal.&lt;/li&gt;
&lt;li&gt;Logo da Collabora. Ganhei durante o encontro da empresa. Infelizmente é
magnético, então eu tenho medo de usar na rua e perder.&lt;/li&gt;
&lt;li&gt;Equador. Presente de um amigo que visitou esse lugar, o mesmo escalador que
48, percebeu o padrão? 😝&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Até então eu tenho guardado meus pins em uma caixa com tampa de vidro que fica
em cima de uma mesa. Eu já pensei em deixar eles em um quadro na
parede para que eles ficassem mais visíveis, mas fiquei preocupado de acabarem
estragando com o tempo por estarem expostos o tempo todo.&lt;/p&gt;
&lt;p&gt;Mas eu também não deixo eles trancados na caixa permanentemente, porque isso
seria um desperdício, então eu uso eles na roupa. Especificamente na touca, que
é onde funciona melhor para mim (Veja a primeira foto). Por esse motivo eu não
consigo usar pins durante o verão (ainda estou pensando em uma alternativa). Eu
sempre uso um único pin porque acho que mais que isso fica bagunçado, e mudo
frequentemente e tento combinar com a ocasião.&lt;/p&gt;
&lt;p&gt;Agora que essa caixa está cheia eu vou precisar comprar uma nova (ou talvez
tentar um quadro dessa vez). Eu provavelmente vou publicar outro artigo quando
esse encher também, mas isso provavelmente vai levar um tempo. Eu já tenho
uma longa lista de pins para comprar, mas estou tentando maneirar. O foco é a
qualidade e não a quantidade, e eu não quero chegar em um ponto onde não consigo
apreciar cada um deles.&lt;/p&gt;
</content><category term="2023"/><category term="pin"/></entry><entry><title>Encontrando com a comunidade Linux na minha primeira ELC</title><link href="https://nfraprado.net/pt-br/post/encontrando-com-a-comunidade-linux-na-minha-primeira-elc.html" rel="alternate"/><published>2022-09-29T00:00:00-03:00</published><updated>2022-09-29T00:00:00-03:00</updated><author><name>Nícolas F. R. A. Prado</name></author><id>tag:nfraprado.net,2022-09-29:/pt-br/post/encontrando-com-a-comunidade-linux-na-minha-primeira-elc.html</id><summary type="html">&lt;p&gt;Esse mês eu participei da Embedded Linux Conference Europe, que ocorreu junto
com a Open Source Summit Europe, em Dublin, na Irlanda. Essa foi minha primeira
conferência de Linux presencial como contribuidor e foi uma experiência muito
especial.&lt;/p&gt;
&lt;p&gt;Mas essa não foi minha primeira conferência de Linux presencial na vida …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Esse mês eu participei da Embedded Linux Conference Europe, que ocorreu junto
com a Open Source Summit Europe, em Dublin, na Irlanda. Essa foi minha primeira
conferência de Linux presencial como contribuidor e foi uma experiência muito
especial.&lt;/p&gt;
&lt;p&gt;Mas essa não foi minha primeira conferência de Linux presencial na vida. Em 2018
eu participei da linuxdev-br, que ocorreu justamente na Unicamp, onde eu estava
fazendo graduação. Eu me lembro de ter assistido a apresentação do Sergio Prado
sobre como escrever um driver de LED e ter ficado fascinado com o fato de um LED
físico poder ser ligado e desligado simplesmente escrevendo em um arquivo.&lt;/p&gt;
&lt;p&gt;Essa conferência foi um daqueles momentos que mudam sua vida dali para frente,
já que foi lá que eu conheci a Helen Koike, que me convidou para participar dos
encontros semanais do &lt;a class="reference external" href="https://lkcamp.dev/"&gt;LKCAMP&lt;/a&gt;, onde eles estavam ensinando sobre como o kernel
funciona e ajudando as pessoas a submeterem seus primeiros &lt;em&gt;patches&lt;/em&gt; e se
tornarem contribuidoras. E depois de aprender bastante com eles e ter algumas
contribuições aceitas eu eventualmente entrei na Collabora.&lt;/p&gt;
&lt;p&gt;De volta para 2022 e aqui estava eu em Dublin, para minha segunda conferência de
Linux presencial na vida, dessa vez uma bem maior, e tendo trabalhado por mais
de um ano na Collabora, participando como um contribuidor ativo do kernel Linux.&lt;/p&gt;
&lt;p&gt;Como um contribuidor, eu estou acostumado a interagir com outros
desenvolvedores do kernel através das listas de email no dia-a-dia. E apesar de
isso funcionar eficientemente para o projeto, é difícil se sentir conectado de
uma forma humana com a comunidade lidando apenas com respostas em texto e
endereços de email.&lt;/p&gt;
&lt;p&gt;E é por isso que encontrar com essas pessoas na vida real foi tão revigorante. É
uma ótima mudança de ritmo poder conversar com elas de uma forma mais expressiva
e humana, não apenas sobre trabalho mas sobre a vida no geral. E também de fato
poder vê-las com meus próprios olhos e confirmar que são pessoas de verdade,
com suas próprias vidas, aspirações, preferências de comida. Me lembrou que o
mundo inteiro realmente é construido por pessoas como eu.&lt;/p&gt;
&lt;p&gt;Também foi minha primeira vez encontrando com meus colegas de trabalho da
Collabora, o que foi espetacular! Foto &lt;a class="reference external" href="https://twitter.com/Collabora/status/1570761841089073153"&gt;aqui&lt;/a&gt;. Sou eu ali na esquerda 🙂.&lt;/p&gt;
&lt;p&gt;A Linux Foundation publicou o álbum de fotos do evento &lt;a class="reference external" href="https://www.flickr.com/photos/linuxfoundation/albums/72177720301869820"&gt;aqui&lt;/a&gt; caso queira ver.&lt;/p&gt;
&lt;p&gt;Eu mal voltei da viagem e já mal posso esperar pela próxima oportunidade de ver
todo mundo de novo!&lt;/p&gt;
&lt;img alt="{image}/convention-center-night.jpg" src="/images/elce/convention-center-night.jpg" /&gt;
&lt;p&gt;(O centro de convenções de Dublin iluminando a noite, do outro lado do rio
Liffey)&lt;/p&gt;
</content><category term="2022"/><category term="conferência"/></entry><entry><title>Mudando o blog para o Codeberg</title><link href="https://nfraprado.net/pt-br/post/mudando-o-blog-para-o-codeberg.html" rel="alternate"/><published>2022-08-29T00:00:00-03:00</published><updated>2022-08-29T00:00:00-03:00</updated><author><name>Nícolas F. R. A. Prado</name></author><id>tag:nfraprado.net,2022-08-29:/pt-br/post/mudando-o-blog-para-o-codeberg.html</id><summary type="html">&lt;p&gt;Eu me importo bastante com FOSS, por isso sempre prefiro mudar para uma
alternativa FOSS quando uma nova surge, desde que não haja grandes desvantagens.&lt;/p&gt;
&lt;p&gt;Na época da faculdade quando eu estava começando a usar o Git, eu só conhecia
duas opções de serviços de Git: GitHub, o mais conhecido …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Eu me importo bastante com FOSS, por isso sempre prefiro mudar para uma
alternativa FOSS quando uma nova surge, desde que não haja grandes desvantagens.&lt;/p&gt;
&lt;p&gt;Na época da faculdade quando eu estava começando a usar o Git, eu só conhecia
duas opções de serviços de Git: GitHub, o mais conhecido mas proprietário, e o
GitLab, menos conhecido mas mais aberto. Entre os dois, o GitLab era a escolha
óbvia para os meus repositórios pessoais, incluindo esse blog.&lt;/p&gt;
&lt;p&gt;Alguns meses atrás eu conheci o &lt;a class="reference external" href="https://codeberg.org/"&gt;Codeberg&lt;/a&gt;. O Codeberg provê uma instância
hospedada do &lt;a class="reference external" href="https://gitea.io/"&gt;Gitea&lt;/a&gt;, que é uma plataforma para repositórios Git completamente
FOSS. Ainda por cima, o Codeberg é mantido por uma ONG, o que deixa claro que
ele é focado na comunidade, e não no dinheiro. Do meu ponto de vista, não tem
como ser melhor do que isso, então eu fiquei bastante motivado a mover todos
meus repositórios para o Codeberg.&lt;/p&gt;
&lt;p&gt;A maioria dos meus repositórios são, na prática, simples backups: Eu sou o único
enviando código para eles, e desde que o histórico de commits esteja disponível,
eles não precisam de nenhuma outra funcionalidade. Por esse motivo a migração
foi bem simples. A única exceção é o repositório do blog.&lt;/p&gt;
&lt;div class="section" id="adaptacao-do-blog-para-o-codeberg"&gt;
&lt;h2&gt;Adaptação do blog para o Codeberg&lt;/h2&gt;
&lt;p&gt;A única grande funcionalidade faltante no Codeberg, e da qual eu dependia no
GitLab para o blog, é a CI.&lt;/p&gt;
&lt;p&gt;Minha configuração atual do blog no GitLab era de ter um repositório com CI
configurada para que toda vez que eu enviasse novas mudanças, a CI rodasse o
pelican para gerar as páginas do site e servisse elas.&lt;/p&gt;
&lt;p&gt;No Codeberg, como não há CI, quaisquer arquivos que estiverem presentes no
repositório especial &lt;code class="docutils literal"&gt;pages&lt;/code&gt; são servidos diretamente. Esse método exigiu
que eu criasse dois repositórios: um para os arquivos fonte, o &lt;a class="reference external" href="https://codeberg.org/nfraprado/blog"&gt;repositório
blog&lt;/a&gt;, e outro para a saída gerada ser servida, o &lt;a class="reference external" href="https://codeberg.org/nfraprado/pages"&gt;repositório pages&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Observação&lt;/strong&gt;: &lt;a class="reference external" href="https://codeberg.page/"&gt;Também é possível&lt;/a&gt; usar um branch &lt;code class="docutils literal"&gt;pages&lt;/code&gt; no mesmo
repositório, mas a opção do repositório separado me pareceu mais organizada.&lt;/p&gt;
&lt;p&gt;Mas claro que eu não queria gerenciar o repositório adicional &lt;code class="docutils literal"&gt;pages&lt;/code&gt;, já que
isso seria irritante e rapidamente me faria sentir falta da CI do GitLab. Por
isso eu adicionei um novo comando &lt;code class="docutils literal"&gt;make deploy&lt;/code&gt; no Makefile para automatizar o
processo de publicar o site no repositório &lt;code class="docutils literal"&gt;pages&lt;/code&gt;. Ele pode ser visto &lt;a class="reference external" href="https://codeberg.org/nfraprado/blog/commit/44bb31065898a942164a8a23c526d9363c750d93"&gt;nesse
commit&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;O que o &lt;code class="docutils literal"&gt;make deploy&lt;/code&gt; faz é:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Fazer os passos anteriormente feitos no &lt;a class="reference external" href="https://codeberg.org/nfraprado/blog/src/commit/38cc4cfa0b5bb34eb5d088210abbce5cbf553ff7/.gitlab-ci.yml"&gt;.gitlab-ci.yml&lt;/a&gt; para gerar os
arquivos de saída do blog, especificamente:&lt;ul&gt;
&lt;li&gt;Gerar as strings de tradução (através do &lt;code class="docutils literal"&gt;make trans_deploy&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Gerar as páginas do site (através do &lt;code class="docutils literal"&gt;make publish&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Commitar os arquivos de saída e fazer push deles para o repositório &lt;code class="docutils literal"&gt;pages&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Já que os arquivos de saída gerados pelo pelican precisam ser commitados e
enviados para o repositório &lt;code class="docutils literal"&gt;pages&lt;/code&gt;, a pasta local de saída dentro da pasta do
blog agora precisa ser um repositório git. Para permitir isso, a variável
&lt;code class="docutils literal"&gt;OUTPUT_RETENTION&lt;/code&gt; &lt;a class="reference external" href="https://codeberg.org/nfraprado/blog/commit/44bb31065898a942164a8a23c526d9363c750d93"&gt;precisou ser atualizada&lt;/a&gt; para que
o pelican não delete a pasta &lt;code class="docutils literal"&gt;.git&lt;/code&gt; toda vez que ele rodar.&lt;/p&gt;
&lt;p&gt;Em seguida eu melhorei as mensagens de commit para o repositório &lt;code class="docutils literal"&gt;pages&lt;/code&gt;
através &lt;a class="reference external" href="https://codeberg.org/nfraprado/blog/commit/38cc4cfa0b5bb34eb5d088210abbce5cbf553ff7"&gt;desse commit&lt;/a&gt;, formatando elas com tanto o hash quanto a descrição,
&lt;a class="reference external" href="https://www.kernel.org/doc/html/latest/process/submitting-patches.html#describe-your-changes"&gt;no mesmo formato&lt;/a&gt; que é usado para as tags &lt;code class="docutils literal"&gt;Fixes:&lt;/code&gt; no kernel Linux. Isso
me permite não apenas correlacionar todo commit no repositório &lt;code class="docutils literal"&gt;pages&lt;/code&gt; ao
commit correspondente no repositório &lt;code class="docutils literal"&gt;blog&lt;/code&gt;, mas também facilmente identificar
sobre o que foram as últimas mudanças.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="https://codeberg.org/nfraprado/blog/commit/19f8d66f86038744362b6c9d23a33607e0b8ac84"&gt;Eu também adicionei&lt;/a&gt; um arquivo &lt;code class="docutils literal"&gt;.domains&lt;/code&gt; à retenção já que ele é
necessário para usar um domínio customizado com o Codeberg.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusao"&gt;
&lt;h2&gt;Conclusão&lt;/h2&gt;
&lt;p&gt;No começo a necessidade de versionar os arquivos de saída com Git me pareceu
desagradável, mas o fato de ser em um repositório separado e ser fácil
relacionar os commits aos fontes fez com que isso deixasse de ser um problema
para mim.&lt;/p&gt;
&lt;p&gt;Inclusive, eu percebi duas vantagens nessa estratégia do repositório &lt;code class="docutils literal"&gt;pages&lt;/code&gt;
em relação a ter uma CI. Primeiro, eu não preciso mais pensar nas versões de
imagens e pacotes que estão sendo usados na CI porque não há CI. Desde que meu
sistema local seja capaz de gerar o blog, tudo continuará funcionando (e eu já
precisava ter uma configuração local de toda forma para conseguir testar as
mudanças antes de enviar). Segundo, assim que eu rodo &lt;code class="docutils literal"&gt;make deploy&lt;/code&gt; as
mudanças são aplicadas instantaneamente no site, enquanto na CI do GitLab
levariam em torno de um minuto para atualizar.&lt;/p&gt;
&lt;p&gt;Então foram necessárias algumas pequenas mudanças para ter uma boa configuração
do blog no Codeberg mas eu estou feliz com a mudança. O Codeberg parece um ótimo
novo lar e eu vou fazer questão de cuidar bem dele 🙂.&lt;/p&gt;
&lt;/div&gt;
</content><category term="2022"/><category term="blog"/><category term="codeberg"/></entry><entry><title>Usando emojis no matplotlib</title><link href="https://nfraprado.net/pt-br/post/usando-emojis-no-matplotlib.html" rel="alternate"/><published>2022-07-20T00:00:00-03:00</published><updated>2022-07-20T00:00:00-03:00</updated><author><name>Nícolas F. R. A. Prado</name></author><id>tag:nfraprado.net,2022-07-20:/pt-br/post/usando-emojis-no-matplotlib.html</id><summary type="html">&lt;p&gt;Mês passado, enquanto eu escrevia o artigo com todas as estatísticas para o
aniversário de dois anos do blog, o &lt;a class="reference external" href="/pt-br/post/estatisticas-do-blog-apos-dois-anos.html"&gt;artigo "Estatísticas do blog após dois anos"&lt;/a&gt;, eu decidi que
eu queria um gráfico com emojis. Assim que eu pensei nisso eu sabia que não
iria funcionar de primeira e …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Mês passado, enquanto eu escrevia o artigo com todas as estatísticas para o
aniversário de dois anos do blog, o &lt;a class="reference external" href="/pt-br/post/estatisticas-do-blog-apos-dois-anos.html"&gt;artigo "Estatísticas do blog após dois anos"&lt;/a&gt;, eu decidi que
eu queria um gráfico com emojis. Assim que eu pensei nisso eu sabia que não
iria funcionar de primeira e que eu estava me condenando a algumas horas
investigando erros.&lt;/p&gt;
&lt;p&gt;Na primeira tentativa de gerar um gráfico com emojis, foi isso que saiu:&lt;/p&gt;
&lt;img alt="{image}/emojis-traced.png" src="/images/matplotlib-emojis/emojis-traced.png" /&gt;
&lt;p&gt;Esses emojis só tem o contorno e alguns estão até faltando. Eu queria que o
matplotlib usasse uma fonte própria para emoji, como a NotoColorEmoji ou
twemoji, que são coloridas e bonitas. As duas estavam instaladas no meu sistema
mas não estavam sendo detectadas automaticamente pelo matplotlib.&lt;/p&gt;
&lt;p&gt;Depois de pesquisar um pouco eu descobri como adicionar uma fonte explicitamente
ao matplotlib:&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="n"&gt;matplotlib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;font_manager&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fontManager&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;addfont&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/usr/share/fonts/noto/NotoColorEmoji.ttf&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;E eu também adicionei &lt;code class="docutils literal"&gt;fontname=&amp;quot;noto color emoji&amp;quot;&lt;/code&gt; à chamada do matplotlib
que deveria renderizar usando essa fonte (no meu caso &lt;code class="docutils literal"&gt;xticks()&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Forçando o matplotlib a usar a fonte de emoji que eu queria desse jeito
realmente fez os emojis só com contorno pararem de aparecer, mas também fez a
imagem inteira parar de aparecer 😝, agora a única coisa que eu tinha era
traceback:&lt;/p&gt;
&lt;pre class="code literal-block"&gt;
 Traceback (most recent call last):
   File &amp;quot;/home/nfraprado/emoji.py&amp;quot;, line 3, in &amp;lt;module&amp;gt;
     matplotlib.font_manager.fontManager.addfont('/usr/share/fonts/noto/NotoColorEmoji.ttf')
   File &amp;quot;/usr/lib/python3.10/site-packages/matplotlib/font_manager.py&amp;quot;, line 1092, in addfont
     font = ft2font.FT2Font(path)
 RuntimeError: In FT2Font: Could not set the fontsize (error code 0x17)
&lt;/pre&gt;
&lt;p&gt;Com código de erro até, me convidando a entrar mais fundo 😆. Como essa área de
fontes não me era familiar, a primeira coisa a fazer era descobrir é o que é o
FT2Font. &lt;a class="reference external" href="https://github.com/matplotlib/matplotlib/blob/main/src/ft2font.cpp"&gt;Navegando no fonte&lt;/a&gt; descobri que ele é o código do matplotlib
responsável por lidar com o &lt;a class="reference external" href="https://freetype.org/"&gt;FreeType&lt;/a&gt;, a biblioteca que renderiza as fontes.&lt;/p&gt;
&lt;p&gt;A mensagem de erro do traceback estava vindo &lt;a class="reference external" href="https://github.com/matplotlib/matplotlib/blob/60ae76b6b5b071e45d1ab652464f68a592cf977d/src/ft2font.cpp#L354"&gt;dessa linha&lt;/a&gt;, o que significa
que &lt;code class="docutils literal"&gt;FT_Set_Char_Size()&lt;/code&gt;, que é uma função do freetype, estava falhando com o
código de erro &lt;code class="docutils literal"&gt;0x17&lt;/code&gt;. Uma checada na &lt;a class="reference external" href="https://freetype.org/freetype2/docs/reference/ft2-error_code_values.html"&gt;referência de códigos de erro do
freetype&lt;/a&gt; mostrou que ele significava &amp;quot;invalid pixel size&amp;quot;.&lt;/p&gt;
&lt;p&gt;Enquanto eu tentava descobrir o que diferia as fontes de emoji de fontes
normais, eu reparei em algo que parecia ser fundamental: Rodar
&lt;code class="docutils literal"&gt;fc-scan&lt;/code&gt; nas fontes de emoji revelava que elas tinham uma propriedade
&lt;code class="docutils literal"&gt;pixelsize&lt;/code&gt; que não estava presente em fontes normais. Além disso, rodar
&lt;code class="docutils literal"&gt;ftview&lt;/code&gt; (do pacote &lt;code class="docutils literal"&gt;freetype2-demos&lt;/code&gt;) nas fontes de emoji passando qualquer
tamanho resultava nos emojis sendo desenhados sempre do mesmo tamanho, enquanto
fontes normais eram redimensionadas corretamente.&lt;/p&gt;
&lt;p&gt;A essa altura eu precisava realmente entrar no código, então eu clonei o fonte
do matplotlib e do freetype, compilei, e configurei o ambiente para que eu
conseguisse usá-los: instalei o matplotlib em um ambiente virtual com &lt;code class="docutils literal"&gt;pip
install -e .&lt;/code&gt; e apontei a variável de ambiente &lt;code class="docutils literal"&gt;LD_LIBRARY_PATH&lt;/code&gt; para a pasta
contendo os arquivos resultantes da compilação do freetype.&lt;/p&gt;
&lt;p&gt;Depois de habilitar os logs de debug no freetype (com &lt;code class="docutils literal"&gt;FT2_DEBUG=any:5&lt;/code&gt;) e
adicionar alguns logs meus, eu percebi que a diferença no código que roda para
as fontes de emoji é por conta de &lt;code class="docutils literal"&gt;FT_HAS_FIXED_SIZES&lt;/code&gt; ser verdadeiro, cujo
significado pode ser visto &lt;a class="reference external" href="https://gitlab.freedesktop.org/freetype/freetype/-/blob/e7482ff4c2a39e0e6bcf32b90ccfbfdd0f8ef5e6/include/freetype/freetype.h#L1372"&gt;aqui&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Então resumindo o problema (até onde eu entendo): as fontes de emoji são
compostas por imagens embutidas, que possuem um tamanho específico. Isso é
indicado com &lt;code class="docutils literal"&gt;FT_HAS_FIXED_SIZES&lt;/code&gt; sendo verdadeiro, e o tamanho dessas imagens
aparece como o atributo &lt;code class="docutils literal"&gt;pixelsize&lt;/code&gt;. Quando o matplotlib vai desenhar a fonte,
ele especifica o tamanho em que ele quer desenhar a fonte, mas já que a fonte de
emoji tem um tamanho fixo, o código do freetype espera que o tamanho passado
seja o mesmo tamanho da fonte. Isso porque você pode ter múltiplas versões das
imagens, com diferentes tamanhos, dentro de uma mesma fonte, então o freetype
iria escolher aquela que tem o tamanho que foi pedido.&lt;/p&gt;
&lt;p&gt;Com isso em mente, eu fiz essa modificação:&lt;/p&gt;
&lt;pre class="code diff literal-block"&gt;
&lt;span class="gh"&gt;diff --git a/src/base/ftobjs.c b/src/base/ftobjs.c&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="gh"&gt;index f66273f3d3cb..a59a61119702 100644&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="gd"&gt;--- a/src/base/ftobjs.c&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/src/base/ftobjs.c&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="gu"&gt;&amp;#64;&amp;#64; -3074,6 +3074,7 &amp;#64;&amp;#64;&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;    FT_Int   i;&lt;span class="w"&gt;
 &lt;/span&gt;    FT_Long  w, h;&lt;span class="w"&gt;
 
&lt;/span&gt;&lt;span class="gi"&gt;+    ignore_width = 1;&lt;/span&gt;&lt;span class="w"&gt;
 
 &lt;/span&gt;    if ( !FT_HAS_FIXED_SIZES( face ) )&lt;span class="w"&gt;
 &lt;/span&gt;      return FT_THROW( Invalid_Face_Handle );&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="gu"&gt;&amp;#64;&amp;#64; -3101,8 +3102,6 &amp;#64;&amp;#64;&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;      FT_Bitmap_Size*  bsize = face-&amp;gt;available_sizes + i;&lt;span class="w"&gt;
 
 
&lt;/span&gt;&lt;span class="gd"&gt;-      if ( h != FT_PIX_ROUND( bsize-&amp;gt;y_ppem ) )&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="gd"&gt;-        continue;&lt;/span&gt;&lt;span class="w"&gt;
 
 &lt;/span&gt;      if ( w == FT_PIX_ROUND( bsize-&amp;gt;x_ppem ) || ignore_width )&lt;span class="w"&gt;
 &lt;/span&gt;      {
&lt;/pre&gt;
&lt;p&gt;O que essa mudança faz é forçar o freetype a retornar a primeira opção de
imagem dentro da fonte, mesmo que ela não tenha o tamanho que foi pedido.&lt;/p&gt;
&lt;p&gt;Com isso feito, um outro erro passou a ocorrer, agora no matplotlib:&lt;/p&gt;
&lt;pre class="code literal-block"&gt;
Traceback (most recent call last):
  File &amp;quot;/home/nfraprado/emoji.py&amp;quot;, line 3, in &amp;lt;module&amp;gt;
    matplotlib.font_manager.fontManager.addfont('/usr/share/fonts/noto/NotoColorEmoji.ttf')
  File &amp;quot;/home/nfraprado/matplotlib/lib/matplotlib/font_manager.py&amp;quot;, line 1134, in addfont
    prop = ttfFontProperty(font)
  File &amp;quot;/home/nfraprado/matplotlib/lib/matplotlib/font_manager.py&amp;quot;, line 545, in ttfFontProperty
    raise NotImplementedError(&amp;quot;Non-scalable fonts are not supported&amp;quot;)
NotImplementedError: Non-scalable fonts are not supported
&lt;/pre&gt;
&lt;p&gt;Então só agora o matplotlib estava reclamando que a fonte não é redimensionável,
o que deveria ter sido o erro desde o início... Enfim, eu estava curioso para
ver se isso tudo iria funcionar no fim, então eu simplesmente removi essa
checagem:&lt;/p&gt;
&lt;pre class="code diff literal-block"&gt;
&lt;span class="gh"&gt;diff --git a/lib/matplotlib/font_manager.py b/lib/matplotlib/font_manager.py&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="gh"&gt;index f57fc9c051b0..08d8c28752cb 100644&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="gd"&gt;--- a/lib/matplotlib/font_manager.py&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/lib/matplotlib/font_manager.py&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="gu"&gt;&amp;#64;&amp;#64; -541,8 +541,6 &amp;#64;&amp;#64; def ttfFontProperty(font):&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;    #  Length value is an absolute font size, e.g., 12pt&lt;span class="w"&gt;
 &lt;/span&gt;    #  Percentage values are in 'em's.  Most robust specification.&lt;span class="w"&gt;
 
&lt;/span&gt;&lt;span class="gd"&gt;-    if not font.scalable:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="gd"&gt;-        raise NotImplementedError(&amp;quot;Non-scalable fonts are not supported&amp;quot;)&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;    size = 'scalable'&lt;span class="w"&gt;
 
 &lt;/span&gt;    return FontEntry(font.fname, name, style, variant, weight, stretch, size)
&lt;/pre&gt;
&lt;p&gt;E finalmente obtive alguma saída:&lt;/p&gt;
&lt;img alt="{image}/emojis-bw-unscaled.png" src="/images/matplotlib-emojis/emojis-bw-unscaled.png" /&gt;
&lt;p&gt;Os emojis estão em preto e branco, e não estão do tamanho certo, mas é um
avanço. Nessa hora, minha saída estava parecida com a que eu tinha visto em um
&lt;a class="reference external" href="https://towardsdatascience.com/how-i-got-matplotlib-to-plot-apple-color-emojis-c983767b39e0"&gt;artigo de blog&lt;/a&gt; e &lt;a class="reference external" href="https://github.com/matplotlib/matplotlib/issues/12830"&gt;issue no Github&lt;/a&gt; linkada enquanto estava pesquisando. O
problema nesse caso era o formato TTC, então não era a mesma coisa, mas a
alternativa de usar um outro backend que tivesse um suporte melhor para emojis,
&lt;a class="reference external" href="https://github.com/matplotlib/mplcairo"&gt;mplcairo&lt;/a&gt;, parecia promissora.&lt;/p&gt;
&lt;p&gt;Então eu instalei o mplcairo com o pip e fiz o script usar ele com&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="n"&gt;matplotlib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;module://mplcairo.gtk&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;E essa foi a saída:&lt;/p&gt;
&lt;img alt="{image}/emojis-colored-scaled.png" src="/images/matplotlib-emojis/emojis-colored-scaled.png" /&gt;
&lt;p&gt;Então os emojis realmente renderizaram coloridos e com o tamanho certo com esse
backend! O único problema era que o texto no eixo Y estava rotacionado, mas uma
busca rápida mostrou que &lt;a class="reference external" href="https://github.com/matplotlib/mplcairo/issues/35"&gt;isso já estava consertado no repositório&lt;/a&gt;, então eu
reinstalei o mplcairo do git e tudo funcionou perfeitamente!&lt;/p&gt;
&lt;p&gt;Quando eu comecei a investigar esse problema eu pensei que fosse acabar enviando
alguma modificação consertando a fonte, o freetype ou o matplotlib, mas com o
meu entendimento atual, eu não tenho certeza qual seria a forma correta de
consertar o problema. Eu precisaria investigar um pouco mais para saber, mas
dado que eu consegui fazer funcionar para o meu uso, apesar de com algumas
gambiarras, eu provavelmente não vou mais investigar esse problema por enquanto.&lt;/p&gt;
&lt;p&gt;Dito isso, o meu entendimento é que o mplcairo faz o redimensionamento da fonte
por conta própria para fontes de tamanho fixo (emojis), então fazer o
mplcairo checar os tamanhos disponíveis na fonte e passar para o freetype aquele
que for mais próximo do tamanho que vai ser desenhado, poderia ser uma forma de
se livrar da gambiarra no freetype. Já quanto à gambiarra no matplotlib, talvez
fontes de tamanho fixo poderiam ser permitidas quando o o backend mplcairo
estivesse sendo usado, já que ele claramente sabe lidar com elas. Mas, de novo,
mais investigação necessária para concluir de fato.&lt;/p&gt;
&lt;p&gt;De qualquer forma, com essas duas gambiarras mostradas aqui e o backend
mplcairo, eu consegui gerar o gráfico com os emojis mais frequentemente usados
que está no final do &lt;a class="reference external" href="/pt-br/post/estatisticas-do-blog-apos-dois-anos.html"&gt;artigo "Estatísticas do blog após dois anos"&lt;/a&gt;. O script completo que gerou
esse gráfico pode ser visto &lt;a class="reference external" href="https://codeberg.org/nfraprado/blog/src/commit/f6c10780301d53661eff67052e180b9705d94610/extra/plot-top-emoji.py"&gt;aqui&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Por fim, para garantir a reprodutibilidade, vale mencionar que tudo isso foi
testado com os pacotes nos seguintes commits:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;mplcairo: &lt;a class="reference external" href="https://github.com/matplotlib/mplcairo/commit/74c27c3dbd54c6c59fa0fd212d9c2bacd3275649"&gt;74c27c3dbd54&lt;/a&gt; (&amp;quot;Tweak path search in build-windows-wheel.&amp;quot;)&lt;/li&gt;
&lt;li&gt;matplotlib: &lt;a class="reference external" href="https://github.com/matplotlib/matplotlib/commit/60ae76b6b5b071e45d1ab652464f68a592cf977d"&gt;60ae76b6b5b0&lt;/a&gt; (&amp;quot;Merge pull request #23243 from timhoffm/take-over-22839&amp;quot;)&lt;/li&gt;
&lt;li&gt;freetype: &lt;a class="reference external" href="https://gitlab.freedesktop.org/freetype/freetype/-/commit/e7482ff4c2a39e0e6bcf32b90ccfbfdd0f8ef5e6"&gt;e7482ff4c2a3&lt;/a&gt; (&amp;quot;* src/lzw/ftzopen.c (ft_lzwstate_stack_grow): Cosmetic macro change.&amp;quot;)&lt;/li&gt;
&lt;/ul&gt;
</content><category term="2022"/><category term="matplotlib"/><category term="emoji"/></entry><entry><title>Estatísticas do blog após dois anos</title><link href="https://nfraprado.net/pt-br/post/estatisticas-do-blog-apos-dois-anos.html" rel="alternate"/><published>2022-06-20T00:00:00-03:00</published><updated>2022-06-20T00:00:00-03:00</updated><author><name>Nícolas F. R. A. Prado</name></author><id>tag:nfraprado.net,2022-06-20:/pt-br/post/estatisticas-do-blog-apos-dois-anos.html</id><summary type="html">&lt;p&gt;Uau, fazem dois anos desde que eu comecei esse blog! Estou muito feliz que
consegui manter o ritmo de um artigo por mês pelo segundo ano. Espero que
consiga manter pelo terceiro!&lt;/p&gt;
&lt;p&gt;Para esse artigo eu pensei que seria divertido ver algumas estatísticas sobre o
que eu fiz no blog …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Uau, fazem dois anos desde que eu comecei esse blog! Estou muito feliz que
consegui manter o ritmo de um artigo por mês pelo segundo ano. Espero que
consiga manter pelo terceiro!&lt;/p&gt;
&lt;p&gt;Para esse artigo eu pensei que seria divertido ver algumas estatísticas sobre o
que eu fiz no blog até agora. Para isso eu &lt;a class="reference external" href="https://codeberg.org/nfraprado/blog/commit/c372aa64ce2049d42df0df4f908955905e89411b"&gt;criei um plugin 'stats'&lt;/a&gt; para o
pelican que gera as estatísticas em que estou interessado, e &lt;a class="reference external" href="https://codeberg.org/nfraprado/blog/commit/f6c10780301d53661eff67052e180b9705d94610"&gt;escrevi
scripts em python usando o matplotlib&lt;/a&gt; para gerar gráficos dos dados
coletados.&lt;/p&gt;
&lt;div class="section" id="graficos-e-estatisticas"&gt;
&lt;h2&gt;Gráficos e estatísticas&lt;/h2&gt;
&lt;p&gt;Eu consegui manter o ritmo mensal de publicação, mas não consegui sempre
publicar no dia 20 do mês como eu pretendia, e isso fica aparente nesse gráfico:&lt;/p&gt;
&lt;img alt="{image}/dom-publish_br.png" src="/images/2years-stats/dom-publish_br.png" /&gt;
&lt;p&gt;(Abra a imagem em uma nova aba para ver melhor)&lt;/p&gt;
&lt;p&gt;No começo eu ainda não tinha decidido uma data para publicar todo mês, mas
começando com o artigo de setembro de 2020 eu decidi que seria o dia 20 e
consegui manter essa meta, quase sempre, até maio do ano seguinte. Depois disso
eu praticamente nunca mais publiquei no dia 20, e existe até uma certa tendência
ascendente a partir daí, com os artigos deslizando cada vez mais para o fim dos
meses.&lt;/p&gt;
&lt;p&gt;Esse outro gráfico mostra a quantidade total acumulada em cada dia do mês:&lt;/p&gt;
&lt;img alt="{image}/dom-publish-count_br.png" src="/images/2years-stats/dom-publish-count_br.png" /&gt;
&lt;p&gt;O dia 20 ainda é o dia em que eu mais publiquei, com 8 no total, mas a maior
parte dos artigos estão distribuídos praticamente uniformemente nos dias após
ele, totalizando 17! Ou seja, só um terço dos artigos, aproximadamente, foram
de fato publicados no dia 20 como eu queria.&lt;/p&gt;
&lt;p&gt;Mas isso não me incomoda muito. Seria legal ter mantido a publicação dos artigos
consistentes no mesmo dia do mês, mas publicar todo mês independente do dia é
muito mais importante para mim, e eu estou bem feliz que tive sucesso nisso.&lt;/p&gt;
&lt;p&gt;Mudando um pouco de assunto, eu sempre quis saber a quantidade de palavras de
cada artigo, então aqui está:&lt;/p&gt;
&lt;img alt="{image}/word-count_br.png" src="/images/2years-stats/word-count_br.png" /&gt;
&lt;p&gt;Não existe nenhuma tendência óbvia, o que era de se esperar. O menor artigo de
todos foi o &lt;a class="reference external" href="/pt-br/post/configurando-o-mbsync-para-usar-xoauth2.html"&gt;artigo "Configurando o mbsync para usar XOAUTH2"&lt;/a&gt; com 553 palavras em inglês e 567
palavras em português, enquanto o maior de todos foi o &lt;a class="reference external" href="/pt-br/post/gerenciando-minhas-tarefas-usando-o-vit.html"&gt;artigo "Gerenciando minhas tarefas usando o VIT"&lt;/a&gt; com
surpreendentes 2727 palavras em inglês e 2747 palavras em português. O segundo
maior artigo foi o &lt;a class="reference external" href="/pt-br/post/domando-meu-kindle.html"&gt;artigo "Domando meu Kindle"&lt;/a&gt; não muito abaixo, enquanto os
demais ficaram todos abaixo das 2000 palavras.&lt;/p&gt;
&lt;p&gt;Esse gráfico também permite comparar a quantidade de palavras entre inglês e
português, e fica claro que a versão em português é quase sempre um pouco maior.
Isso confirma a minha percepção, já que várias vezes eu lembro de ter aumentado
o tamanho da frase tentando expressar em português algo que em inglês eu
consegui expressar com um único termo técnico comum.&lt;/p&gt;
&lt;p&gt;No total, todos os artigos em inglês somam 32718 palavras, enquanto todos os em
português somam 33488 palavras. Logo, meus artigos em português são em média
2% maiores, o que honestamente é menos do que eu esperava.&lt;/p&gt;
&lt;p&gt;Importante mencionar que essa contagem de palavras é feita na etapa final de
geração do artigo, quando ele já está em sua forma HTML, o que significa que
todo o conteúdo que aparece nele após publicado está lá, com a diferença de que
as tags HTML que poderiam adicionar à contagem são removidas. Na prática isso
significa que a contagem total de palavras do artigo é o texto mais os blocos de
código. Portanto um elemento importante que não aparece nesse gráfico são as
imagens, que podem ser vistas a seguir:&lt;/p&gt;
&lt;img alt="{image}/num-images_br.png" src="/images/2years-stats/num-images_br.png" /&gt;
&lt;p&gt;Nem todos os artigos tem imagens, mas alguns deles tem muitas delas! Inclusive,
o &lt;a class="reference external" href="/pt-br/post/domando-meu-kindle.html"&gt;artigo "Domando meu Kindle"&lt;/a&gt;, que é o segundo maior artigo, é o que possui
mais imagens, com 16 no total!&lt;/p&gt;
&lt;p&gt;Essa parte é bem arbitrária, mas eu queria levar em consideração o número de
imagens na comparação de quantidade de conteúdo entre artigos. Eles dizem que
uma imagem vale mais que mil palavras, mas no meu caso isso parece um pouco
exagerado. Ao invés disso, contar cada imagem como 75 palavras, um parágrafo
normal, parece mais razoável. Além disso, decidi fazer GIFs valerem três vezes
mais, 225 palavras, já que o fato de serem animados os torna muito mais
ricos. Com isso em mente, eu gerei um gráfico com a estimativa de conteúdo total
de todos os artigos:&lt;/p&gt;
&lt;img alt="{image}/total-content_br.png" src="/images/2years-stats/total-content_br.png" /&gt;
&lt;p&gt;Mais uma vez, os números são totalmente subjetivos, e isso ainda não leva em
consideração os arquivos de áudio que eu compus e linkei no
&lt;a class="reference external" href="/pt-br/post/aprendendo-teoria-musical-escrevendo-melodias.html"&gt;artigo "Aprendendo teoria musical escrevendo melodias"&lt;/a&gt;, nem os códigos que eu escrevi em algum repositório e
que apenas linkei no blog, como no caso do &lt;a class="reference external" href="/pt-br/post/gerenciando-meus-pacotes.html"&gt;artigo "Gerenciando meus pacotes"&lt;/a&gt;. Mas dadas as
limitações, eu diria que essa estimativa está muito mais próxima da minha
percepção de quanto conteúdo cada artigo possui.&lt;/p&gt;
&lt;p&gt;Uma notável diferença é que nesse novo gráfico, o
&lt;a class="reference external" href="/pt-br/post/domando-meu-kindle.html"&gt;artigo "Domando meu Kindle"&lt;/a&gt; se tornou o artigo com mais conteúdo.&lt;/p&gt;
&lt;p&gt;Enfim, de volta a métricas mais objetivas, eu gerei um gráfico com o número de
links para outros artigos, ou referências cruzadas, que cada artigo possui:&lt;/p&gt;
&lt;img alt="{image}/num-article-links_br.png" src="/images/2years-stats/num-article-links_br.png" /&gt;
&lt;p&gt;A maioria dos artigos não faz referência a outros (esperado), e os que fazem,
são no máximo duas. Dito isso, este artigo apareceria muito mais alto do que o
restante nesse gráfico, mas eu diria que ele é uma exceção, já que é um artigo
instrospectivo sobre o blog.&lt;/p&gt;
&lt;p&gt;E links quaisquer?&lt;/p&gt;
&lt;img alt="{image}/num-links_br.png" src="/images/2years-stats/num-links_br.png" /&gt;
&lt;p&gt;Todo artigo tem pelo menos um link, o que não é de se espantar, essa é a
internet afinal de contas!&lt;/p&gt;
&lt;p&gt;E finalmente, a pergunta mais importante, quantos emojis??&lt;/p&gt;
&lt;img alt="{image}/num-emojis_br.png" src="/images/2years-stats/num-emojis_br.png" /&gt;
&lt;p&gt;Não tão comuns quanto links (felizmente?), mas também nada mal. Aparentemente eu
estou ficando  mais consistente nos meus emojis, o que quer que isso signifique.&lt;/p&gt;
&lt;p&gt;Mas de que vale saber quantos emojis estão sendo usados se não pudermos ver
quais?&lt;/p&gt;
&lt;img alt="{image}/top-emoji_br.png" src="/images/2years-stats/top-emoji_br.png" /&gt;
&lt;p&gt;Claro que o sorriso é o mais comum, difícil não sorrir quando se está falando de
algo em que está interessado. No segundo lugar temos o rosto com a língua para
fora para os momentos engraçados. E mais alguns emojis aleatórios.&lt;/p&gt;
&lt;p&gt;Esse gráfico foi bem mais difícil de gerar do que parece, mas mais sobre ele no
mês que vem (provavelmente).&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusao"&gt;
&lt;h2&gt;Conclusão&lt;/h2&gt;
&lt;p&gt;Apesar de ser necessário um certo esforço para conseguir escrever sobre algo
todo mês, eu realmente gosto do resultado. Esse ritual mensal se mostrou
terapêutico para mim, já que me dá a oportunidade de construir um novo tijolo
nesse espaço seguro que é o meu blog, então eu sinto que estou avançando em algo
na minha vida mesmo quando mais nada acontece.&lt;/p&gt;
&lt;p&gt;A necessidade de um artigo todo mês também me incentiva a continuar fazendo as
coisas que me interessam, porque se não eu não vou ter sobre o que escrever!&lt;/p&gt;
&lt;/div&gt;
</content><category term="2022"/><category term="aniversário-blog"/><category term="blog"/></entry><entry><title>Descobrindo o conforto de alto-falantes</title><link href="https://nfraprado.net/pt-br/post/descobrindo-o-conforto-de-alto-falantes.html" rel="alternate"/><published>2022-05-27T00:00:00-03:00</published><updated>2022-05-27T00:00:00-03:00</updated><author><name>Nícolas F. R. A. Prado</name></author><id>tag:nfraprado.net,2022-05-27:/pt-br/post/descobrindo-o-conforto-de-alto-falantes.html</id><summary type="html">&lt;p&gt;Em 2014, durante uma viagem escolar para a Alemanha, eu comprei um fone de
ouvido Razer Tiamat. O som era ótimo e no geral era bem confortável. O único
problema era que ele apertava bastante a cabeça e me dava dor de cabeça depois
de longas horas de uso.&lt;/p&gt;
&lt;p&gt;Esse …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Em 2014, durante uma viagem escolar para a Alemanha, eu comprei um fone de
ouvido Razer Tiamat. O som era ótimo e no geral era bem confortável. O único
problema era que ele apertava bastante a cabeça e me dava dor de cabeça depois
de longas horas de uso.&lt;/p&gt;
&lt;p&gt;Esse fone me serviu bastante e eu usei ele até 2021. A essa altura, o couro das
almofadas já tinha desgastado quase completamente e o conector p2 quebrou e
precisou soldar um novo duas vezes. Uma das vezes o contato de um dos fios não
estava muito bom, o que fez com que um lado ficasse mais alto que o outro e
precisou ser compensado em software, mas a compensação necessária mudava
conforme o volume mudava, o que me deixou doido e eu lembro de ter ficado
absurdamente feliz quando eu finalmente consertei isso.&lt;/p&gt;
&lt;p&gt;Então em 2021 já estava mais do que na hora de um novo fone. Já que a pressão na
cabeça era o meu maior problema, eu queria achar um que apertasse o mínimo
possível e que ainda tivesse um preço razoável.&lt;/p&gt;
&lt;div class="section" id="a-procura-de-um-fone-confortavel"&gt;
&lt;h2&gt;À procura de um fone confortável&lt;/h2&gt;
&lt;p&gt;Pesquisando na internet eu encontrei um site chamado Rtings que analiza e
classifica fones, e um dos critérios é o quanto ele aperta (&lt;em&gt;Clamping force&lt;/em&gt;).
Eu filtrei a lista por esse critério e decidi pegar o HyperX Cloud Alpha já que
ele era um dos que menos apertava e não era muito caro.&lt;/p&gt;
&lt;p&gt;Mas quando ele chegou, eu fiquei bem decepcionado. Ele apertava muito mais do
que o Tiamat e me dava dor de cabeça bem rápido depois de uma ou duas horas de
uso. Eu tentei deixar ele esticando de um dia para o outro, mas não fez nenhuma
diferença. Então eventualmente eu comecei a evitar a usar o fone e usar os
alto-falantes embutidos do notebook, que soam muito mal, mas pelo menos não
machucam.&lt;/p&gt;
&lt;p&gt;Alguns meses depois eu tinha me mudado para os EUA e tinha uma gama muito maior
de opções à disposição, então eu decidi tentar de novo. Eu não queria errar uma
segunda vez, então eu fiz questão de experimentar o maior número possível de
fones em lojas físicas, e a B&amp;amp;H era a maior que eu conhecia.&lt;/p&gt;
&lt;p&gt;Mas para minha surpresa, quase todos eles apertavam o suficiente para serem
desconfortáveis para mim. Até o Bose QuietComfort 35, que pelas minhas pesquisas
era um dos fones mais confortáveis, era um pouco apertado para mim. Mas eu achei
sim alguns dos fones confortáveis, como o Sony Mdr7506, Phillips HP9500 e Koss
UR20. E curioso que eles eram mais baratos que a maioria também. Mas esses fones
eram tão soltos que parecia que iriam cair facilmente. Aparentemente eu não
conseguia achar um meio termo para a pressão do fone.&lt;/p&gt;
&lt;p&gt;Nessa hora eu comecei a pensar que talvez meu problema era inerente a fones. Eu
já tinha pensado em fones intra-auriculares também, mas o problema deles é que
eles deformam dentro do seu ouvido, causando outro tipo de dor. Então de repente
eu pensei em alto-falantes. Será que eles seriam a minha solução?&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="procurando-um-bom-alto-falante"&gt;
&lt;h2&gt;Procurando um bom alto-falante&lt;/h2&gt;
&lt;p&gt;Pesquisando eu encontrei &lt;a class="reference external" href="https://www.nytimes.com/wirecutter/reviews/best-computer-speakers/"&gt;um ranking de alto-falantes bem pesquisado e testado
da Wirecutter&lt;/a&gt;. Aparentemente essa é uma série bem conhecida de rankings, mas
eu nunca tinha ouvido falar então fiquei feliz de encontrar.&lt;/p&gt;
&lt;p&gt;Eu cheguei bem perto de comprar um dos falantes mais recomendados deles, mas eu
senti que seria mais importante ter um subwoofer do que a resposta em frequência
ser plana como eles estavam priorizando, já que meu caso de uso é basicamente
ouvir música, e não produzir.&lt;/p&gt;
&lt;p&gt;Eles tinham como &amp;quot;Also great&amp;quot; o Klipsch ProMedia 2.1 THX, que tem subwoofer.
Pesquisando em outros rankings, eu percebi que apesar dos primeiros colocados
serem diferentes, esse falante estava consistentemente aparecendo na maioria
deles. E aparentemente ele é vendido já há uns bons anos, então se ele continua
relevante nesses rankings, deve realmente ser bom.&lt;/p&gt;
&lt;p&gt;As desvantagens do Klipsch geralmente mencionavam a necessidade de espaço extra
para o subwoofer espaçoso e a falta de conectividade com por exemplo bluetooth -
a entrada de áudio é somente através de um cabo p2. Eu não vi nenhum problema
quanto ao espaço: o subwoofer vai no chão, e lá eu tenho espaço de sobra! Quanto
à entrada de áudio: eu ia deixar o falante conectado constantemente na porta
&amp;quot;Line Out&amp;quot; da doca do meu notebook, então sem problemas também.&lt;/p&gt;
&lt;p&gt;Por fim, para ter uma ideia melhor do quão importante um subwoofer seria para
mim, eu abri algumas músicas que eu gosto e que tem bastante grave no Audacity e
usei o efeito &amp;quot;Filter Curve&amp;quot; e tentei imitar o gráfico de resposta em frequência
que o artigo da Wirecutter mostrou para um dos falantes que não tinha subwoofer
para ver como soaria. Isso se mostrou útil e ficou claro que sem as baixas
frequências, essas músicas perderam bastante substância. Então eu concluí que
realmente queria um subwoofer, e o Klipsch parecia ser o melhor falante com um,
então eu escolhi ele.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="o-klipsch-promedia-2-1-thx"&gt;
&lt;h2&gt;O Klipsch ProMedia 2.1 THX&lt;/h2&gt;
&lt;p&gt;E olha... Essa deve ter sido uma das minhas compras mais felizes na vida.
Primeiro de tudo, por causa da mudança de fone para alto-falante. Eu nem
acredito que finalmente posso ouvir música &lt;em&gt;o dia inteiro&lt;/em&gt; sem sentir
&lt;em&gt;absolutamente nada&lt;/em&gt; na minha cabeça. Isso é &lt;em&gt;mágico&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Segundo, o subwoofer realmente traz vida para a música. Não é &lt;em&gt;toda&lt;/em&gt; música que
faz uso dele, mas mais músicas do que eu pensei que seria. Dá para ouvir (e
sentir!) ele principalmente no baixo e na percussão das músicas. O Klipsch tem
um ajuste de volume separado para o subwoofer e eu fico diminuindo e aumentando
ele para comparar a diferença que faz e realmente é impressionante.&lt;/p&gt;
&lt;p&gt;Quanto às desvantagens, o Klipsch tem um LED bem brilhante para mostrar que
está ligado, mas não tem um botão para desligar, ou seja, esse LED fica sempre
ligado. Mas um pedacinho de fita isolante em cima resolve o problema.&lt;/p&gt;
&lt;p&gt;Outra coisa é que agora que eu tenho um alto-falante tão bom preso na minha
mesa, eu sinto falta dele quando levo meu notebook para um outro cômodo. Se
tivesse um jeito de trazer ele comigo de uma forma portátil seria ótimo... Mas
aí voltamos ao conceito de fone de ouvido e já sabemos onde esse caminho dá 😝.
Eu também sinto um pouco de falta do isolamento sonoro (nos dois sentidos) de um
fone, mas isso é bem menos importante do que conforto para mim.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusao"&gt;
&lt;h2&gt;Conclusão&lt;/h2&gt;
&lt;p&gt;Eu acho interessante que quando eu era criança, o computador na minha casa tinha
um par de alto-falantes como era de costume. Mas desde então eu só tive, e vi
outras pessoas usarem, fones de ouvido, então eu incoscientemente passei a
considerá-los como a única opção. Perceber que esse não é o caso, apesar de
alto-falantes hoje em dia aparentemente serem bem menos comuns, e voltar a
usá-los é até poético de certa forma.&lt;/p&gt;
&lt;p&gt;Eu ainda tenho meu odiado fone de ouvido e eu uso ele para chamadas, já que o
eco dos alto-falantes seria muito ruim. Mas assim que a chamada termina eu fico
&lt;em&gt;muito&lt;/em&gt; feliz em tirar o fone e voltar para o conforto dos meus alto-falantes 😌.&lt;/p&gt;
&lt;/div&gt;
</content><category term="2022"/><category term="compra"/><category term="conforto"/><category term="som"/></entry><entry><title>Aprendendo com o SerenityOS</title><link href="https://nfraprado.net/pt-br/post/aprendendo-com-o-serenityos.html" rel="alternate"/><published>2022-04-29T00:00:00-03:00</published><updated>2022-04-29T00:00:00-03:00</updated><author><name>Nícolas F. R. A. Prado</name></author><id>tag:nfraprado.net,2022-04-29:/pt-br/post/aprendendo-com-o-serenityos.html</id><summary type="html">&lt;p&gt;Um dia eu estava navegando pelo Reddit como de costume, quando eu vi uma
postagem linkando para esse artigo: &lt;a class="reference external" href="https://awesomekling.github.io/I-quit-my-job-to-focus-on-SerenityOS-full-time/"&gt;I quit my job to focus on SerenityOS full
time&lt;/a&gt;. Eu fiquei intrigado pela história, pela premissa desse SO, e também pelo
fato do seu desenvolvimento estar sendo &lt;a class="reference external" href="https://www.youtube.com/c/AndreasKling/videos"&gt;gravado no Youtube …&lt;/a&gt;&lt;/p&gt;</summary><content type="html">&lt;p&gt;Um dia eu estava navegando pelo Reddit como de costume, quando eu vi uma
postagem linkando para esse artigo: &lt;a class="reference external" href="https://awesomekling.github.io/I-quit-my-job-to-focus-on-SerenityOS-full-time/"&gt;I quit my job to focus on SerenityOS full
time&lt;/a&gt;. Eu fiquei intrigado pela história, pela premissa desse SO, e também pelo
fato do seu desenvolvimento estar sendo &lt;a class="reference external" href="https://www.youtube.com/c/AndreasKling/videos"&gt;gravado no Youtube&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Agora, quase um ano depois, eu assisti quase todos os vídeos que o Andreas
publicou no canal desde então e alguns dos mais antigos também. Os vídeos dele
säo muito bons, eles são divertidos, inspiradores e eu aprendi muito com eles. E
ele é uma pessoa muito boa também. O fato de um projeto tão positivo ter surgido
da própria terapia dele é demais. Na minha opinião ele definitivamente achou seu
propósito na vida e eu estou muito feliz por ele.&lt;/p&gt;
&lt;p&gt;O SerenityOS é um projeto extremamente interessante por si só. Ele contém código
para o kernel, bibliotecas, serviços e aplicações, tudo em um único repositório.
Ele te convida a se familizarizar com o sistema inteiro e ver como cada
componente interage com os outros e se encaixa no todo. Além do mais existe uma
variedade tão grande nos tipos de problemas que cada componente está tentando
resolver que parece que sempre tem algo interessante para explorar no Serenity.&lt;/p&gt;
&lt;p&gt;Mas ter tudo no mesmo lugar não é útil só para aprender sobre o código. Quando
você faz o sistema inteiro, é possível fazer algumas integrações muito legais.
Algumas das minhas funcionalidades preferidas do Serenity são:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Todos os dados expostos pelo kernel através de arquivos no estilo procfs são
no formato JSON!&lt;/li&gt;
&lt;li&gt;O aplicativo Profile Viewer não só é capaz de mostrar perfis de desempenho do
sistema inteiro com as pilhas de execução em árvore como também até
identificadores colocados pelas aplicações amostradas para demarcar eventos
interessantes!&lt;/li&gt;
&lt;li&gt;Existe uma bela linguagem de marcação para descrever a GUI dos aplicativos,
GML, e o aplicativo GML Playground mostra uma visualização em tempo real
conforme você escreve!&lt;/li&gt;
&lt;li&gt;Em &lt;em&gt;todas&lt;/em&gt; aplicações (a não ser que explicitamente desabilitado) é possível
abrir uma paleta de comandos, com todos os comandos da aplicação.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Outra vantagem em fazer o sistema inteiro é que mudar APIs é muito mais fácil,
já que eles controlam ambos os lados. Isso permite iterar mais rapidamente e que
melhores formas de fazer as coisas evoluam com o tempo sem haver necessidade de
manter as iterações anteriores para manter a retrocompatibilidade. Dito isso,
essa flexibilidade não se estende para a ABI e interações com o usuário. E
apesar de até o momento essas também terem sido alteradas livremente, eu não sei
o quanto disso é apenas devido ao SerenityOS ainda ser visto como em
desenvolvimento e não haverem usuários de verdade. Mas talvez sendo um
sistema &amp;quot;por nós, para nós&amp;quot; como diz o README, a habilidade de melhorar até
mesmo na ABI pode ser vista como mais importante do que mantê-la estável. O
tempo dirá.&lt;/p&gt;
&lt;p&gt;Quanto às minhas contribuições, até agora eu fiz apenas algumas, principalmente,
&lt;a class="reference external" href="https://github.com/SerenityOS/serenity/pull/12841"&gt;o suporte mais básico possível para mostrar capas de álbum no Sound Player&lt;/a&gt; e
&lt;a class="reference external" href="https://github.com/SerenityOS/serenity/pull/12764"&gt;adição de suporte para múltiplos layouts de teclado no aplicativo Keyboard
Settings&lt;/a&gt;. Mas eu me diverti bastante trabalhando nelas, e estou ansioso para
contribuir mais.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Atualização&lt;/strong&gt;: no dia seguinte da publicação desse artigo a minha contribuição
no Sound Player foi mostrada no &lt;a class="reference external" href="https://youtu.be/yUmHEYs5n34?t=2105"&gt;vídeo de atualização do SerenityOS de Abril&lt;/a&gt;!
😃&lt;/p&gt;
&lt;p&gt;Uma coisa que realmente me fez pensar foi o objetivo do Serenity de ter uma GUI
no estilo de 1990. Quando eu comecei a usar e aprender sobre Linux alguns anos
atrás, eu imediatamente fui atraído pelos programas CLI e TUI e preferi eles
desde então. Eles representavam um paradigma bem diferente dos programas GUI que
eu estava acostumado no mundo Windows. No terminal não havia medo de expor todas
as funcionalidades de uma maneira bem flexível, existia muito mais espaço para
customização e a performance tendia a ser melhor.&lt;/p&gt;
&lt;p&gt;Mas depois de ver a abordagem do Serenity, eu acho que meu desprezo por
aplicações GUI tem sido equivocado. Com a mentalidade certa, programas GUI podem
sim ser bastante flexíveis, customizáveis e ter boa performance. E além disso
podem ter uma aparência consistente e estética agradável. O que mais você quer
de um software? Em resumo, eu agora estou confiante de que eu prefiriria usar
GUIs para a maioria das tarefas, é só que as que estão por aí não tendem a focar
nas coisas certas para mim.&lt;/p&gt;
&lt;p&gt;O SerenityOS ainda passa a sensação de um brinquedo. E é bem divertido brincar
com ele. Mas eu também concordo com muitos dos seus objetivos, e ele está
crescendo muito rápido, portanto é um SO bem promissor para mim. Por um lado, eu
gostaria que ele já estivesse pronto para o uso cotidiano para que eu pudesse
mudar para ele logo, mas por outro, isso tiraria toda a diversão de chegar lá
(tanto em contribuir eu mesmo quanto assistir aos vídeos do Andreas!). Fora que
é mais fácil contribuir quando ainda tem bastante coisa para fazer 🙂.&lt;/p&gt;
</content><category term="2022"/><category term="serenityos"/></entry><entry><title>Rodando LineageOS no meu Nexus 5X</title><link href="https://nfraprado.net/pt-br/post/rodando-lineageos-no-meu-nexus-5x.html" rel="alternate"/><published>2022-03-30T00:00:00-03:00</published><updated>2022-03-30T00:00:00-03:00</updated><author><name>Nícolas F. R. A. Prado</name></author><id>tag:nfraprado.net,2022-03-30:/pt-br/post/rodando-lineageos-no-meu-nexus-5x.html</id><summary type="html">&lt;p&gt;Esse mês completa um ano desde que eu comprei um Nexus 5X e comecei a usar
LineageOS, então parece um bom momento para compartilhar minha experiência.&lt;/p&gt;
&lt;div class="section" id="compra-e-instalacao"&gt;
&lt;h2&gt;Compra e instalação&lt;/h2&gt;
&lt;p&gt;Aproximadamente um ano atrás, &lt;a class="reference external" href="https://andrealmeid.com/"&gt;um amigo&lt;/a&gt; me disse que tinha achado uma oferta
muito boa para um Nexus 5X online. O …&lt;/p&gt;&lt;/div&gt;</summary><content type="html">&lt;p&gt;Esse mês completa um ano desde que eu comprei um Nexus 5X e comecei a usar
LineageOS, então parece um bom momento para compartilhar minha experiência.&lt;/p&gt;
&lt;div class="section" id="compra-e-instalacao"&gt;
&lt;h2&gt;Compra e instalação&lt;/h2&gt;
&lt;p&gt;Aproximadamente um ano atrás, &lt;a class="reference external" href="https://andrealmeid.com/"&gt;um amigo&lt;/a&gt; me disse que tinha achado uma oferta
muito boa para um Nexus 5X online. O Nexus 5X já é um celular antigo, lançado em
2015, e por isso tende a ser bem barato, apesar de ainda ser um celular bem
decente mesmo para os padrões atuais. A oferta que ele encontrou era para um
Nexus 5X &lt;em&gt;novo&lt;/em&gt;, que vinha dentro da caixa original, perfeitamente conservado, e
ainda sim custava apenas R$ 300. Um ótimo negócio.&lt;/p&gt;
&lt;p&gt;Nesse ponto eu já estava usando Linux no meu computador há vários anos, mas meu
celular, possivelmente a máquina que tem acesso à maior quantidade de
informações pessoais, ainda estava rodando Android, que é um SO que não é
completamente FOSS, e isso me incomodava bastante.&lt;/p&gt;
&lt;p&gt;Eu já tinha tido vontade de mudar para o LineageOS, a alternativa FOSS ao
Android, há algum tempo, mas como era meu único celular parecia muito arriscado.
Com a possibilidade de ter um novo Nexus 5X de repente eu vi a oportunidade de
ter um celular bem testado (e amado) com LineageOS, e se tudo desse errado, meu
celular antigo ainda estaria lá com tudo exatamente do mesmo jeito. Então eu
decidi pegar um Nexus para mim também.&lt;/p&gt;
&lt;p&gt;Eu segui o &lt;a class="reference external" href="https://andrealmeid.com/post/2020-11-23-bullhead-los/"&gt;guia de Nexus 5X do meu amigo&lt;/a&gt; e tive um pouco de ajuda pessoal
dele para entender os componentes de software necessários para rodar uma
instalação de LineageOS totalmente sem Google. No meu caso o celular era novo,
então eu pude usar uma imagem sem o patch de BLOD e ter acesso à performance
total do Nexus 5X.&lt;/p&gt;
&lt;p&gt;A instalação em si foi fácil de seguir (tudo bem que eu tinha acesso a um guia
e um mentor). E ao final eu tinha meu próprio celular com LineageOS, MicroG e
Magisk, completamente sem Google e ainda sim capaz de instalar software que
dependesse do Google Services quando necessário.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="problemas"&gt;
&lt;h2&gt;Problemas&lt;/h2&gt;
&lt;p&gt;No geral a experiência é ótima, mas tem dois problemas principais:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;strong&gt;Problemas frequentes na câmera&lt;/strong&gt;: Basicamente toda vez que eu abro a câmera,
ela fecha na primeira vez, e eu preciso abrir de novo e aí ela funciona.
Irritante, mas ainda não o suficiente para eu ir tentar consertar.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Suporte de GPS errático&lt;/strong&gt;: O GPS foi a coisa mais difícil de fazer
funcionar. Eu lembro de ter visto algumas pistas &lt;a class="reference external" href="https://blog.eowyn.net/unifiednlp/"&gt;nesse artigo&lt;/a&gt;, e
finalmente consegui fazer funcionar simplesmente habilitando os módulos de
localização &amp;quot;Mozilla Location Service&amp;quot; e &amp;quot;WiFi Location Service&amp;quot;, mas nem
sempre funcionava e demorava um tempo inicial antes de começar a funcionar. E
aí eu mudei de país e agora não consigo mais fazer funcionar...&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Mas esses são problemas de software que podem eventualmente ser consertados. Tem
uma única limitação de hardware no Nexus 5X que me deixou decepcionado assim que
eu notei: &lt;strong&gt;Ele só tem 16GB de armazenamento interno e não tem entrada para
cartão SD&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Para os aplicativos, 16GB na verdade é até ok, mas para tirar fotos ou vídeos e
armazenar música, não era o suficiente.&lt;/p&gt;
&lt;div class="section" id="procurando-por-mais-espaco"&gt;
&lt;h3&gt;Procurando por mais espaço&lt;/h3&gt;
&lt;p&gt;Mas eu não desisti logo de cara. O celular estava tão perto de ser perfeito, eu
precisava achar um jeito de contornar a falta de espaço de armazenamento.&lt;/p&gt;
&lt;p&gt;O Nexus 5X tem uma porta USB-C OTG, então eu percebi que poderia comprar um
pendrive USB-C, o menor possível para que não fosse muito intrusivo, para
efetivamente expandir o espaço de armazenamento.&lt;/p&gt;
&lt;p&gt;Depois de pesquisar um pouco, o pendrive USB-C mais compacto que eu encontrei
foi o &lt;a class="reference external" href="https://www.amazon.com/Kingston-Digital-32GB-Traveler-DTDUO3C/dp/B01M59PHE4"&gt;Kingston DTDUO3C/128GB&lt;/a&gt;. Então foi ele que eu comprei. Logo que ele
chegou eu notei que ele vinha com uma cordinha para chaveiro e a capa do meu
celular tinha uma cavidade para isso, então eu amarrei ele lá e achei que ficou
bonitinho. Dito isso tem algumas desvantagens: o pendrive as vezes engacha no
meu bolso, e também bate na tela. Ficou assim:&lt;/p&gt;
&lt;img alt="{image}/usb_disconnected.jpg" src="/images/nexus5x/usb_disconnected.jpg" /&gt;
&lt;img alt="{image}/usb_connected.jpg" src="/images/nexus5x/usb_connected.jpg" /&gt;
&lt;p&gt;Mas assim que eu inseri o pendrive eu fiquei desapontado ao ver que o tocador de
música não reconhecia nenhuma música que estava lá. Aparentemente o pendrive
estava funcionando apenas como um simples armazenamento e eu só conseguia mover
arquivos de/para ele usando o gerenciador de arquivos.&lt;/p&gt;
&lt;p&gt;Mas como isso era um problema estranho, eu pensei que deveria ter um jeito de
contorná-lo. Afinal, era só uma limitação arbitrária imposta pelo Android.
Depois de um pouco de pesquisa, de fato, &lt;a class="reference external" href="https://forum.xda-developers.com/t/enable-apps-to-read-otg-usb-contents-in-marshmallow-nougat.3539389/"&gt;um artigo no fórum do XDA veio para o
resgate&lt;/a&gt; com uma solução de praticamente uma linha:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;su
sm&lt;span class="w"&gt; &lt;/span&gt;set-force-adoptable&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;E só com isso, reinserindo o pendrive fez o tocador de música rapidamente
começar a indexar todos os arquivos de música que ele tinha acabado de encontrar
no pendrive. Funcionou!&lt;/p&gt;
&lt;p&gt;Mas tem um grande porém: eu preciso sempre lembrar de inserir o pendrive antes
de abrir o tocador de música, e fechar ele antes de remover o pendrive. Caso
contrário o tocador não enxerga mais nenhuma música no dispositivo, e na próxima
vez que eu inserir o pendrive, ele demora alguns minutos indexando todos os
arquivos de novo (e travando algumas vezes durante o processo).&lt;/p&gt;
&lt;p&gt;Dado que eu precisava que meus arquivos de música (e possivelmente fotos) fossem
sincronizados com o computador, eu também estava interessado em fazer o
Syncthing funcionar com pastas no pendrive.&lt;/p&gt;
&lt;p&gt;Para a minha surpresa isso não funcionou de primeira: O Syncthing só tem
permissão de leitura em armazenamentos externos. Mas eu logo descobri que dando
permissão de root para ele remove essa limitação, e isso pode ser feito pelo
Magisk. Não é a solução ideal, mas pelo menos com isso eu tenho uma configuração
funcional para sincronizar e tocar músicas no celular!&lt;/p&gt;
&lt;p&gt;Eu também estava curioso para ver se eu conseguiria fazer a câmera salvar fotos
e vídeos diretamente no pendrive. Isso resolveria a maior parte do problema de
pouco espaço no celular.&lt;/p&gt;
&lt;p&gt;Para não alterar as configurações do aplicativo de câmera principal, eu baixei o
aplicativo Simple Camera e configurei ele para salvar em uma pasta no pendrive.
Para minha surpresa ele simplesmente pediu permissão de escrita para o pendrive,
e não precisou ser rodado como root como o Syncthing.&lt;/p&gt;
&lt;p&gt;Então eu tentei gravar um vídeo com ele. Na primeira tentativa ele deu um erro e
pediu para escolher a qualidade de imagem e eu abaixei para 1080p. Eu acho que a
velocidade de escrita do USB não era rápida o suficiente para a resolução
original da câmera.&lt;/p&gt;
&lt;p&gt;Com isso feito, eu consegui tirar fotos e vídeos. Logo depois que eu paro a
gravação do vídeo o aplicativo fecha, mas o arquivo parece ser corretamente
escrito no pendrive e sincronizado com o computador.&lt;/p&gt;
&lt;p&gt;Mas no fim até o momento eu tenho evitado usar o pendrive para a câmera (usando
o aplicativo de câmera original), porque, diferentemente de música, tirar
fotos/vídeos é situacional, e eu não quero arriscar que minhas fotos falhem ao
salvar ou precisem de múltiplas tentativas (já basta o aplicativo travando...).
De qualquer forma é bom saber que ele fuciona, até certo ponto, para quando eu
quiser usar a câmera e o armazenamento interno estiver cheio.&lt;/p&gt;
&lt;div class="section" id="pendrive-como-armazenamento-interno"&gt;
&lt;h4&gt;Pendrive como armazenamento interno&lt;/h4&gt;
&lt;p&gt;Mas algo mais aconteceu quando eu habilitei a configuração &lt;code class="docutils literal"&gt;force-adoptable&lt;/code&gt;.
Navegando nas configurações, aparentemente que agora eu podia formatar o
pendrive como um armazenamento interno. Isso faria com que a memória do pendrive
fosse usada transparentemente, até para armazenar os aplicativos instalados. Mas
uma pergunta veio à mente: &amp;quot;O que aconteceria quando eu removesse o pendrive?&amp;quot;.
Só tinha um jeito de descobrir se isso seria usável ou não...&lt;/p&gt;
&lt;p&gt;Levou em torno de 30 minutos para o pendrive ser formatado e a maioria dos dados
do armazenamento interno ser movido para ele. Mas no final eu tinha extendido o
armazenamento do meu celular em 128GB com sucesso. E a sensação foi de... muita
lentidão. O sistema inteiro ficava dando travadas o tempo todo. Pelo visto a
velocidade do pendrive é muito mais lenta do que a do armazenamento interno. E
agora que o sistema dependia dessas transferências isso ficava bem aparente.&lt;/p&gt;
&lt;p&gt;Mas talvez eu conseguisse viver com um celular lento em troca de mais espaço de
armazenamento, né? E a grande pergunta: o que acontece quando o pendrive é
removido? Isso é muito importante de saber já que eu precisaria removê-lo no
mínimo para carregar uma vez por dia. E a resposta é: ele não gosta nem um pouco
disso. O sistema reinicia 😝.&lt;/p&gt;
&lt;p&gt;Eu passei quase três dias assim: Com um celular com um armazenamento gigante de
128GB, mas que era lento e reiniciava toda vez que eu precisava recarregá-lo (o
que também acontecia mais frequentemente já que o pendrive aparentemente consome
bastante bateria).&lt;/p&gt;
&lt;p&gt;No final desses três dias eu já estava cansado disso e então reformatei o
pendrive como armazenamento externo e voltei para a configuração anterior. Foi
um experimento interessando (apesar de frustrante).&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="recomendacoes-de-aplicativos"&gt;
&lt;h2&gt;Recomendações de aplicativos&lt;/h2&gt;
&lt;p&gt;Em questão de aplicativos, eu tento o máximo possível usar aplicativos da loja
&lt;a class="reference external" href="https://f-droid.org/"&gt;F-Droid&lt;/a&gt;, mas tem alguns aplicativos que eu ainda preciso usar da PlayStore.
Felizmente, o aplicativo AuroraStore, que já vem instalado, pelo menos fornece
uma alternativa privativa à PlayStore.&lt;/p&gt;
&lt;p&gt;Dos aplicativos que eu instalei da F-Droid, esses são os que eu realmente gosto:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://f-droid.org/en/packages/de.danoeh.antennapod"&gt;AntennaPod&lt;/a&gt;: Tocador de podcast.&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://f-droid.org/en/packages/com.google.android.diskusage"&gt;DiskUsage&lt;/a&gt;: Visualizador de uso de espaço de armazenamento.&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://f-droid.org/en/packages/eu.depau.etchdroid"&gt;EtchDroid&lt;/a&gt;: Criador de imagem USB. Eu falei sobre ele no &lt;a class="reference external" href="/pt-br/post/revivendo-um-computador-no-meio-do-nada.html"&gt;artigo "Revivendo um computador no meio do nada"&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://f-droid.org/en/packages/ml.docilealligator.infinityforreddit"&gt;Infinity&lt;/a&gt;: Visualizador do Reddit.&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://f-droid.org/en/packages/com.kunzisoft.keepass.libre"&gt;KeePassDX&lt;/a&gt;: Gerenciador de senhas.&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://f-droid.org/en/packages/net.mullvad.mullvadvpn"&gt;Mullvad&lt;/a&gt;: Cliente VPN.&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://f-droid.org/en/packages/org.schabi.newpipe"&gt;NewPipe&lt;/a&gt;: Visualizador do YouTube.&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://f-droid.org/en/packages/net.osmand.plus/"&gt;OsmAnd~&lt;/a&gt;: Visualizador de mapa offline baseado no OpenStreetMap.&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://f-droid.org/en/packages/com.spicychair.weather"&gt;Pluvia&lt;/a&gt;: Visualizador de previsão do tempo.&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://f-droid.org/en/packages/name.boyle.chris.sgtpuzzles/"&gt;Puzzles&lt;/a&gt;: Uma coleção de bons jogos de puzzle.&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://f-droid.org/en/packages/com.nutomic.syncthingandroid"&gt;Syncthing&lt;/a&gt;: Sincronizador de arquivos.&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://f-droid.org/en/packages/com.termux"&gt;Termux&lt;/a&gt;: Terminal.&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://f-droid.org/en/packages/com.poupa.vinylmusicplayer"&gt;Vinyl&lt;/a&gt;: Tocador de música.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusao"&gt;
&lt;h2&gt;Conclusão&lt;/h2&gt;
&lt;p&gt;Eu não sei por quanto tempo vou continuar usando esse celular, mas eu com
certeza não volto mais para o Android. Eu vou ou continuar com o LineageOS or
talvez me aventurar no PostmarketOS, se a experiência de usuário estiver num
nível bem usável até lá (ou não se eu sentir que de repente tenho bastante tempo
livre 😝). De qualquer forma eu vou garantir que meu próximo celular tenha mais
espaço de armazenamento 🙂.&lt;/p&gt;
&lt;/div&gt;
</content><category term="2022"/><category term="nexus5x"/><category term="lineageos"/></entry><entry><title>Aprendendo o básico de assembly x86-64</title><link href="https://nfraprado.net/pt-br/post/aprendendo-o-basico-de-assembly-x86-64.html" rel="alternate"/><published>2022-02-25T00:00:00-03:00</published><updated>2022-02-25T00:00:00-03:00</updated><author><name>Nícolas F. R. A. Prado</name></author><id>tag:nfraprado.net,2022-02-25:/pt-br/post/aprendendo-o-basico-de-assembly-x86-64.html</id><summary type="html">&lt;p&gt;Recentemente eu decidi aprender assembly. Eu já tinha um entendimento razoável
de como funciona graças a algumas aulas que tocavam no assunto na universidade,
mas eu nunca tive a oportunidade de realmente escrever código em assembly.&lt;/p&gt;
&lt;p&gt;Já que meu computador é uma máquina x86-64, eu decidi aprender assembly para
essa …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Recentemente eu decidi aprender assembly. Eu já tinha um entendimento razoável
de como funciona graças a algumas aulas que tocavam no assunto na universidade,
mas eu nunca tive a oportunidade de realmente escrever código em assembly.&lt;/p&gt;
&lt;p&gt;Já que meu computador é uma máquina x86-64, eu decidi aprender assembly para
essa arquitetura, assim eu não precisaria de uma máquina virtual. Eu comecei com
apenas o desejo de botar a mão na massa com código em assembly, sem ter nenhum
objetivo ou projeto em especial.&lt;/p&gt;
&lt;p&gt;No começo eu estava alternando entre tentar coisas e pesquisar na internet só
para entender o suficiente a ponto de conseguir fazer um arquivo mínimo em
assembly que eu conseguisse montar e rodar. Eventualmente eu encontrei o livro
que me guiaria: &lt;a class="reference external" href="https://open.umn.edu/opentextbooks/textbooks/733"&gt;x86-64 Assembly Language Programming with Ubuntu&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Esse livro é grátis, recente e tinha o escopo perfeito para mim: é voltado para
pessoas que já entendem bem de programação, mas que são novas em assembly
x86-64, e ele expõe um pouco de teoria e conceitos, mas também tem bastante
exercícios para aprender praticando.&lt;/p&gt;
&lt;p&gt;Foi bem divertido completar esse livro, e funcionou muito bem para eu criar mais
familiaridade com assembly x86-64. Com certeza ainda tem muito para eu aprender
sobre o assunto, já que o livro apenas dá a base, mas já foi o suficiente para
me ensinar algumas coisas interessantes.&lt;/p&gt;
&lt;div class="section" id="sinal-de-variaveis-e-complemento-de-dois"&gt;
&lt;h2&gt;Sinal de variáveis e complemento de dois&lt;/h2&gt;
&lt;p&gt;A maior lição para mim foi um melhor entendimento sobre o sinal de variáveis. Eu
estou acostumado a ver &lt;code class="docutils literal"&gt;int&lt;/code&gt; e &lt;code class="docutils literal"&gt;unsigned int&lt;/code&gt; em C, e a tomar cuidado para
usar o correto, mas não era claro para mim como isso funcionava no nível do
assembly.&lt;/p&gt;
&lt;p&gt;A primeira coisa a se ter em mente, é que o conceito de tipos presente em
linguagens de alto nível como C (por exemplo se um número é com ou sem sinal) é
completamente ausente no assembly. A memória do computador armazena apenas 0s e
1s, e cabe a você, que está programando, interpretar o que eles significam:
&lt;code class="docutils literal"&gt;01011000&lt;/code&gt; é o número 88, o caractere &lt;code class="docutils literal"&gt;X&lt;/code&gt;, a instrução &lt;code class="docutils literal"&gt;POP AX&lt;/code&gt;? Tendo
apenas o byte, você não consegue nem ter certeza do tamanho: talvez seja na
verdade 8 flags booleanas em um único byte, ou parte de um número de 4 bytes.
Sem ter o contexto, é impossível dizer.&lt;/p&gt;
&lt;p&gt;Se a mesma representação pode significar tanto um número com sinal quanto um sem
sinal, dependendo do contexto, isso significa que quando estiver fazendo
operações com esses números, você que está programando que precisa usar a
variante correta da instrução para fornecer esse contexto para o computador.&lt;/p&gt;
&lt;p&gt;Durante o livro, as instruções aritméticas a seguir foram apresentadas para
números sem sinal:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;code class="docutils literal"&gt;add&lt;/code&gt; soma dois números&lt;/li&gt;
&lt;li&gt;&lt;code class="docutils literal"&gt;sub&lt;/code&gt; subtrai dois números&lt;/li&gt;
&lt;li&gt;&lt;code class="docutils literal"&gt;mul&lt;/code&gt; multiplica dois números&lt;/li&gt;
&lt;li&gt;&lt;code class="docutils literal"&gt;div&lt;/code&gt; divide dois números&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;E as instruções a seguir foram mostradas para comparação entre números sem
sinal:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;code class="docutils literal"&gt;ja&lt;/code&gt; compara dois números e pula se o primeiro for maior que o segundo&lt;/li&gt;
&lt;li&gt;&lt;code class="docutils literal"&gt;jb&lt;/code&gt; compara dois números e pula se o primeiro for menor que o segundo&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;E claro, logo em seguida, as variantes dessas instruções para números com sinal
também foram mostradas:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;code class="docutils literal"&gt;imul&lt;/code&gt; é a variante com sinal do &lt;code class="docutils literal"&gt;mul&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code class="docutils literal"&gt;idiv&lt;/code&gt; é a variante com sinal do &lt;code class="docutils literal"&gt;div&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code class="docutils literal"&gt;jg&lt;/code&gt; é a variante com sinal do &lt;code class="docutils literal"&gt;ja&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code class="docutils literal"&gt;jl&lt;/code&gt; é a variante com sinal do &lt;code class="docutils literal"&gt;jb&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Mas calma, e o &lt;code class="docutils literal"&gt;iadd&lt;/code&gt; e o &lt;code class="docutils literal"&gt;isub&lt;/code&gt;? É aí que tá, o jeito que o x86-64
representa números negativos é usando o sistema de &lt;a class="reference external" href="https://pt.wikipedia.org/wiki/Complemento_para_dois"&gt;complemento de dois&lt;/a&gt;, que
tem a útil propriedade de possibilitar que somas e subtrações sejam feitas
exatamente da mesma forma tanto para números com sinal quanto para sem sinal.&lt;/p&gt;
&lt;p&gt;Isso significa que só existe uma forma de somar, independente do sinal do
número, e é usando &lt;code class="docutils literal"&gt;add&lt;/code&gt;. Não existe &lt;code class="docutils literal"&gt;iadd&lt;/code&gt;. Mesma coisa para subtração.&lt;/p&gt;
&lt;p&gt;Então a conclusão interessante é que para adição e subtração não importa se você
usa &lt;code class="docutils literal"&gt;unsigned int&lt;/code&gt; ou &lt;code class="docutils literal"&gt;int&lt;/code&gt; em variáveis em C. O propósito da palavra-chave
&lt;code class="docutils literal"&gt;unsigned&lt;/code&gt; é dar o contexto que falta para o compilador, para que ele use a
instrução correta em operações com esse número no assembly gerado, e é crucial
para a comparação entre números (&lt;code class="docutils literal"&gt;ja&lt;/code&gt; vs &lt;code class="docutils literal"&gt;jg&lt;/code&gt;, &lt;code class="docutils literal"&gt;jb&lt;/code&gt; vs &lt;code class="docutils literal"&gt;jl&lt;/code&gt;),
multiplicação (&lt;code class="docutils literal"&gt;mul&lt;/code&gt; vs &lt;code class="docutils literal"&gt;imul&lt;/code&gt;) e divisão (&lt;code class="docutils literal"&gt;div&lt;/code&gt; vs &lt;code class="docutils literal"&gt;idiv&lt;/code&gt;). Mas graças
ao complemento de dois, na soma e subtração não tem como errar 🙂.&lt;/p&gt;
&lt;p&gt;Tangente: interessantemente, enquanto eu escrevia esse artigo, eu li na página
da Wikipédia (&lt;a class="reference external" href="https://en.wikipedia.org/wiki/Two%27s_complement"&gt;em inglês&lt;/a&gt;) que o complemento de dois também funciona da mesma
forma para multiplicação, mas só se o sinal dos operandos for primeiramente
estendido. Isso me faz pensar que se a instrução &lt;code class="docutils literal"&gt;mul&lt;/code&gt; sempre fizesse o passo
de extensão do sinal, também não haveria necessidade para uma instrução
&lt;code class="docutils literal"&gt;imul&lt;/code&gt;, mas isso provavelmente aumentaria a complexidade (e custo) do circuito
lógico.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="outros-aprendizados-interessantes"&gt;
&lt;h2&gt;Outros aprendizados interessantes&lt;/h2&gt;
&lt;p&gt;A outra coisa que mais me interessou foi perceber que variáveis locais não são
nada mais do que adicionar mais espaço na pilha. E que isso é feito simplesmente
subtraindo do registrador da pilha &lt;code class="docutils literal"&gt;rsp&lt;/code&gt; o total de bytes necessário para
todas as variáveis no começo de uma subrotina.&lt;/p&gt;
&lt;p&gt;Também interessante foi aprender que existem &lt;a class="reference external" href="https://en.wikipedia.org/wiki/X86_calling_conventions#x86-64_calling_conventions"&gt;convenções de chamada&lt;/a&gt; para
padronizar:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;quais registradores são usados para passar argumentos para subrotinas e em
qual ordem;&lt;/li&gt;
&lt;li&gt;quais registradores podem ser sobrescritos por uma subrotina e quais devem ser
mantidos intactos. No caso do uso destes últimos, o valor do registrador deve
ser primeiro empurrado para a pilha para que depois possa ser recuperado antes
de retornar.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;E quanto à função mágica &lt;code class="docutils literal"&gt;main()&lt;/code&gt; que o compilador de C espera encontrar em
todo programa em C? Assembly não requer compilação, então ela não é necessária,
mas no final das contas uma outra label mágica é esperada pelo ligador:
&lt;code class="docutils literal"&gt;_start&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Outras coisas que foram interessantes de fazer em assembly:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Fazer syscalls&lt;/li&gt;
&lt;li&gt;Tirar vantagem de um overflow de buffer na pilha&lt;/li&gt;
&lt;li&gt;Interagir código assembly com código em C, e vice-versa.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="falta-de-uma-boa-gui"&gt;
&lt;h2&gt;Falta de uma boa GUI&lt;/h2&gt;
&lt;p&gt;Uma coisa que eu senti falta foi de uma boa aplicação GUI quando estava
depurando os programas em assembly. Teria sido muito útil ter uma que mostrasse
os valores de expressões em tooltips quando deixasse o mouse em cima, que
pulasse para labels quando clicasse nelas, etc.&lt;/p&gt;
&lt;p&gt;O livro recomenda usar o DDD, que é uma GUI, mas eu não achei ele muito
agradável de usar e era claramente velho. Então eu acabei usando o GDB junto com
o plugin &lt;a class="reference external" href="https://github.com/longld/peda"&gt;peda&lt;/a&gt;, e funcionou razoavelmente bem, mas por ser uma CLI, cada
inspeção requeria descobrir o comando certo, então levava mais tempo para se
orientar.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusao"&gt;
&lt;h2&gt;Conclusão&lt;/h2&gt;
&lt;p&gt;Essa foi uma ótima experiência e eu espero no futuro aprofundar meu conhecimento
além do nível básico de x86-64. Ver o que está acontecendo no nível do assembly
realmente ajuda a entender melhor as linguagens de alto-nível, e a valorizar as
complexidades que elas escondem!&lt;/p&gt;
&lt;p&gt;Eu subi o código que eu escrevi para todos os exercícios do livro &lt;a class="reference external" href="https://codeberg.org/nfraprado/x86-64-book-exercises"&gt;para esse
repositório&lt;/a&gt;. Eu não acho que vai ser útil para ninguém porque são coisas
simples, mas está lá de qualquer forma.&lt;/p&gt;
&lt;p&gt;O único exercício que eu não consegui completar foi o último. Tinha muito pouca
informação no livro sobre como realizá-lo, e durante a pesquisa sobre o tópico
online eu acabei me desanimando e comecei a aprender sobre outros assuntos. Mas
talvez um dia eu tente de novo. Se você souber como fazê-lo, &lt;a class="reference external" href="/pages/about.html"&gt;entre em
contato&lt;/a&gt;! 🙂&lt;/p&gt;
&lt;p&gt;E apesar de eu não ter conseguido concluir esse último exercício, foi durante a
pesquisa sobre ele que eu acabei aprendendo como usar a sintaxe do &lt;code class="docutils literal"&gt;asm&lt;/code&gt; no
GCC &lt;a class="reference external" href="https://www.felixcloutier.com/documents/gcc-asm.html"&gt;através desse guia&lt;/a&gt;, para escrever código assembly dentro de um arquivo
em C, e também conheci o &lt;a class="reference external" href="https://godbolt.org/"&gt;Compiler Explorer&lt;/a&gt; que parece uma ótima forma de
aprender sobre assembly e C olhando qual assembly é gerado para um certo código
em C, então estou considerando como uma vitória!&lt;/p&gt;
&lt;/div&gt;
</content><category term="2022"/><category term="assembly"/><category term="x86-64"/></entry><entry><title>Deixando links internos no pelican triviais</title><link href="https://nfraprado.net/pt-br/post/deixando-links-internos-no-pelican-triviais.html" rel="alternate"/><published>2022-01-20T00:00:00-03:00</published><updated>2022-01-20T00:00:00-03:00</updated><author><name>Nícolas F. R. A. Prado</name></author><id>tag:nfraprado.net,2022-01-20:/pt-br/post/deixando-links-internos-no-pelican-triviais.html</id><summary type="html">&lt;p&gt;Uma funcionalidade interessante do &lt;a class="reference external" href="https://github.com/getpelican/pelican"&gt;Pelican&lt;/a&gt;, o gerador de site estático que eu
uso para esse blog, é a sintaxe de expansão de links internos usando &lt;code class="docutils literal"&gt;{}&lt;/code&gt;. Ela
está documentada &lt;a class="reference external" href="https://docs.getpelican.com/en/latest/content.html?highlight=static#linking-to-internal-content"&gt;aqui&lt;/a&gt;. Alguns exemplos são &lt;code class="docutils literal"&gt;{filename}&lt;/code&gt;, &lt;code class="docutils literal"&gt;{static}&lt;/code&gt; e
&lt;code class="docutils literal"&gt;{author}&lt;/code&gt;. O propósito da sintaxe é ter apelidos mais curtos e fáceis na hora
de …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Uma funcionalidade interessante do &lt;a class="reference external" href="https://github.com/getpelican/pelican"&gt;Pelican&lt;/a&gt;, o gerador de site estático que eu
uso para esse blog, é a sintaxe de expansão de links internos usando &lt;code class="docutils literal"&gt;{}&lt;/code&gt;. Ela
está documentada &lt;a class="reference external" href="https://docs.getpelican.com/en/latest/content.html?highlight=static#linking-to-internal-content"&gt;aqui&lt;/a&gt;. Alguns exemplos são &lt;code class="docutils literal"&gt;{filename}&lt;/code&gt;, &lt;code class="docutils literal"&gt;{static}&lt;/code&gt; e
&lt;code class="docutils literal"&gt;{author}&lt;/code&gt;. O propósito da sintaxe é ter apelidos mais curtos e fáceis na hora
de linkar com conteúdo interno no blog. Por exemplo, &lt;code class="docutils literal"&gt;{filename}&lt;/code&gt; pode ser
usado para linkar com outros arquivos, como artigos.&lt;/p&gt;
&lt;p&gt;A ideia é boa, mas para o meu uso ela não é o suficiente. Onde eu preciso realmente
de expansão de links internos no meu blog é para imagens, artigos e código.&lt;/p&gt;
&lt;div class="section" id="imagens"&gt;
&lt;h2&gt;Imagens&lt;/h2&gt;
&lt;p&gt;A ideia para imagens é bem simples. Eu escrevo meus artigos em &lt;a class="reference external" href="https://docutils.sourceforge.io/rst.html"&gt;rst&lt;/a&gt;, e é assim
que uma imagem é incluída nesse formato:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;..&lt;/span&gt; &lt;span class="ow"&gt;image&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt; caminho/ate/imagem.jpg
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;O jeito que eu estruturo os arquivos no meu blog (que você pode ver &lt;a class="reference external" href="https://codeberg.org/nfraprado/blog"&gt;no
repositório do blog&lt;/a&gt;), é que dentro da pasta &lt;code class="docutils literal"&gt;content&lt;/code&gt; onde todo conteúdo
fica, os fontes dos artigos podem ser encontrados dentro de &lt;code class="docutils literal"&gt;articles/&amp;lt;ano&amp;gt;/&lt;/code&gt;,
e as imagens podem ser encontradas em &lt;code class="docutils literal"&gt;images/&amp;lt;id_artigo&amp;gt;/&lt;/code&gt;, onde
&lt;code class="docutils literal"&gt;&amp;lt;id_artigo&amp;gt;&lt;/code&gt; é a string que identifica o artigo no qual a imagem aparece (ele
é a propriedade &lt;code class="docutils literal"&gt;trans_id&lt;/code&gt; do artigo, derivada do nome do arquivo e usada para
associar traduções de um mesmo artigo, mas eu estou chamando de &lt;code class="docutils literal"&gt;id_artigo&lt;/code&gt;
aqui já que faz mais sentido nesse contexto).&lt;/p&gt;
&lt;p&gt;Dada essa estrutura, se eu usar um caminho relativo do artigo até uma de suas
imagens, ele teria que ser algo como
&lt;code class="docutils literal"&gt;../../images/&amp;lt;id_artigo&amp;gt;/nome_da_imagem.jpg&lt;/code&gt;. A expansão &lt;code class="docutils literal"&gt;{static}&lt;/code&gt; pode
ser usada para simplificá-lo um pouco:
&lt;code class="docutils literal"&gt;{static}/images/&amp;lt;id_artigo&amp;gt;/nome_da_imagem.jpg&lt;/code&gt;. Mas dá para fazer melhor 🙂.&lt;/p&gt;
&lt;p&gt;Seria muito melhor se esse caminho pudesse ser bastante encurtado. Todas as
imagens estão dentro da pasta &lt;code class="docutils literal"&gt;images/&lt;/code&gt;, então isso deveria estar implícito.
Quer saber, já que já estamos aqui mesmo, por que não fazer a parte
&lt;code class="docutils literal"&gt;&amp;lt;id_artigo&amp;gt;/&lt;/code&gt; ser derivada do id do artigo atual. Isso deixaria o link
perfeito, já que apenas sobraria o nome da imagem, que é a parte dele que é
única.&lt;/p&gt;
&lt;p&gt;O primeiro passo para implementar essa lógica customizada foi adicionar o
&lt;a class="reference external" href="https://github.com/getpelican/pelican-plugins/blob/master/linker/linker.py"&gt;plugin linker&lt;/a&gt; no pelican. Ele permite que você implemente suas próprias
expansões de links &lt;code class="docutils literal"&gt;{}&lt;/code&gt; através de classes em python.&lt;/p&gt;
&lt;p&gt;E o segundo passo foi implementar a expansão &lt;code class="docutils literal"&gt;{image}&lt;/code&gt; através &lt;a class="reference external" href="https://codeberg.org/nfraprado/blog/commit/fb4238f841f9a242bdf5f20badd5bb50ccb221b4"&gt;desse
commit&lt;/a&gt;. &lt;a class="reference external" href="https://codeberg.org/nfraprado/blog/commit/dbaaace5ba0e00e2f378ba8c8d7e80b6c36d4326"&gt;Esse é o commit&lt;/a&gt; onde eu atualizei todos os links de imagem para
usar &lt;code class="docutils literal"&gt;{image}&lt;/code&gt;. Sinta o prazer! Ficou muito mais enxuto 🙂.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="artigos"&gt;
&lt;h2&gt;Artigos&lt;/h2&gt;
&lt;p&gt;Linkar com outros artigos é um pouco mais complicado, mas não tanto. A ideia é,
às vezes eu quero referenciar outro artigo que eu escrevi anteriormente no blog.
Usando a expansão &lt;code class="docutils literal"&gt;{filename}&lt;/code&gt;, não fica tão ruim:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;Eu mostrei isso &lt;span class="s"&gt;`em um artigo anterior`__&lt;/span&gt;.

&lt;span class="p"&gt;..&lt;/span&gt; &lt;span class="nt"&gt;__:&lt;/span&gt; {filename}08-task-context-br.rst
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Mas com certeza poderia ser melhor. A expansão &lt;code class="docutils literal"&gt;{filename}&lt;/code&gt; é relativa ao
arquivo atual, então se eu estou referenciando um artigo do mesmo ano, fica
igual nesse exemplo mesmo, mas referenciar um de outro ano requer um
&lt;code class="docutils literal"&gt;../&amp;lt;ano&amp;gt;/&lt;/code&gt; adicional no caminho. Além disso, eu não deveria precisar escrever
o nome do arquivo inteiro. Idealmente eu só deveria precisar escrever o que é
único ao artigo, ou seja, o seu &lt;code class="docutils literal"&gt;id_artigo&lt;/code&gt;. Sim, até o sufixo da língua
(&lt;code class="docutils literal"&gt;-en&lt;/code&gt; ou &lt;code class="docutils literal"&gt;-br&lt;/code&gt;) pode ser omitido, já que eu posso derivá-lo da língua do
artigo atual.&lt;/p&gt;
&lt;p&gt;Até agora tudo bem, isso poderia ser feito com um pouco mais de lógica além do
que eu fiz para o &lt;code class="docutils literal"&gt;{image}&lt;/code&gt;. Mas já que eu já estou melhorando as coisas, eu
gostaria de aproveitar essa chance para padronizar melhor o texto que eu uso
nesses links. Tudo bem, usar um texto como &amp;quot;em um artigo anterior&amp;quot; se encaixa
bem com o texto à sua volta, mas não fica imediatamente óbvio que o link é para
um outro artigo no meu blog.&lt;/p&gt;
&lt;p&gt;Então a ideia é ter uma expansão de link que não só mapeia para o caminho
correto do artigo, mas também automaticamente muda seu texto para conter o
título do artigo. Um toque final é que eu quero que esse texto leve em
consideração a língua do artigo: se está em português, o formato deve ser
'artigo &amp;quot;Título do artigo&amp;quot;' e se está em inglês, '&amp;quot;Título do artigo&amp;quot; post'.&lt;/p&gt;
&lt;p&gt;Atualizar o texto do link não é algo que o plugin linker consegue fazer por
padrão, então eu primeiro precisei estendê-lo para permitir isso &lt;a class="reference external" href="https://codeberg.org/nfraprado/blog/commit/e6a0ea78e2898b7e7004aff9aa17e6be354dbdd8"&gt;nesse
commit&lt;/a&gt;. Com o mecanismo base feito, eu de fato implementei a nova expansão
&lt;code class="docutils literal"&gt;{article}&lt;/code&gt; &lt;a class="reference external" href="https://codeberg.org/nfraprado/blog/commit/ff4f7fc0cc4a418a19574e41510e8f28a62c5e31"&gt;nesse commit&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Todo esse trabalho vale a pena, já que agora eu posso te mostrar como o
resultado ficou te apontando para o primeiro artigo que eu escrevi nesse blog
usando um simples &lt;code class="docutils literal"&gt;{article}tasklist&lt;/code&gt;: &lt;a class="reference external" href="/pt-br/post/criando-listas-de-filmes-e-jogos-usando-taskwarrior.html"&gt;artigo "Criando listas de filmes e jogos usando Taskwarrior"&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Claro que eu também atualizei todas as referências a artigos no blog para usar
essa nova e excelente expansão &lt;a class="reference external" href="https://codeberg.org/nfraprado/blog/commit/c83e1cc8a8484777776a7b6fd70f758a84c351d1"&gt;nesse commit&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="codigo"&gt;
&lt;h2&gt;Código&lt;/h2&gt;
&lt;p&gt;É aqui que complica... Bem, como você deve saber, é bem comum os artigos do meu
blog, por serem técnicos, terem blocos de código no meio do texto. Eu incluo
esses códigos usando a diretiva rst &lt;code class="docutils literal"&gt;include&lt;/code&gt; e passando para ela o caminho de
um arquivo separado que contém o código.&lt;/p&gt;
&lt;p&gt;O problema é, diferentemente da diretiva rst &lt;code class="docutils literal"&gt;image&lt;/code&gt; e os links rst cujos
alvos aparecem no HTML final, o que torna bem simples editá-los dentro do
pelican, a diretiva &lt;code class="docutils literal"&gt;include&lt;/code&gt; e seu caminho são processados pelo leitor rst em
um estágio anterior. Isso significa que os métodos normais para mudar um link no
pelican não podem ser usados aqui (como o plugin linker).&lt;/p&gt;
&lt;p&gt;Eu poderia colocar o código direto no fonte do artigo ao invés de usar a
diretiva &lt;code class="docutils literal"&gt;include&lt;/code&gt; e evitar todo esse problema, mas quando o código tem mais
do que algumas linhas, eu sinto que isso poluiria muito o arquivo fonte do
artigo.&lt;/p&gt;
&lt;p&gt;Bem, se você já leu o &lt;a class="reference external" href="/pt-br/post/customizacoes-do-blog.html"&gt;artigo "Customizações do blog"&lt;/a&gt; você deve lembrar que eu já
tenho um leitor rst customizado. Então para implementar uma expansão de link
para código, eu precisava extender esse leitor para substituir qualquer
ocorrência de &lt;code class="docutils literal"&gt;{code}&lt;/code&gt; dentro de uma diretiva &lt;code class="docutils literal"&gt;include&lt;/code&gt; pelo caminho do
arquivo de código. Foi isso que eu fiz &lt;a class="reference external" href="https://codeberg.org/nfraprado/blog/commit/8d8f03b5fd5448d1c880f782555f6f4e1717a36c"&gt;nesse commit&lt;/a&gt;, copiando o código do
leitor rst do pelican e fazendo algumas mudanças.&lt;/p&gt;
&lt;p&gt;Vale a pena dizer que esse tipo de customização que estou fazendo (e já estava
fazendo) de sobrescrever o leitor rst não é bem estável. Se houverem mudanças no
leitor rst do pelican, eu posso ter que parar de atualizar o pelican ou
reimplementar as mudanças deles no meu leitor customizado. Mas já que não tinha
um jeito menos intrusivo de implementar essa funcionalidade, eu resolvi correr
esse risco.&lt;/p&gt;
&lt;p&gt;Apesar das mudanças não muito atraentes, no fim tudo vale a pena quando você
vê a melhora nos arquivos fontes dos artigos. &lt;a class="reference external" href="https://codeberg.org/nfraprado/blog/commit/3007137d04992b6720cd88691b25ec7bb5a6995a"&gt;Esse é o commit&lt;/a&gt; onde eu
atualizei eles para usar a nova expansão &lt;code class="docutils literal"&gt;{code}&lt;/code&gt;. Duvido que você diga que
não valeu a pena (por favor não diga).&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusao"&gt;
&lt;h2&gt;Conclusão&lt;/h2&gt;
&lt;p&gt;E aqui estamos. Deu um certo trabalho, mas depois dessas mudanças, nunca foi tão
fácil de fazer links internos no meu blog! Tudo que deixa mais fácil de escrever
artigos para o blog vale muito a pena para mim.&lt;/p&gt;
&lt;/div&gt;
</content><category term="2022"/><category term="blog"/></entry><entry><title>Os menus do meu sistema</title><link href="https://nfraprado.net/pt-br/post/os-menus-do-meu-sistema.html" rel="alternate"/><published>2021-12-28T00:00:00-03:00</published><updated>2021-12-28T00:00:00-03:00</updated><author><name>Nícolas F. R. A. Prado</name></author><id>tag:nfraprado.net,2021-12-28:/pt-br/post/os-menus-do-meu-sistema.html</id><summary type="html">&lt;p&gt;Outro artigo falando sobre algo que eu configurei no meu ambiente de trabalho
alguns anos atrás e que continuo usando até hoje 🙂. Dessa vez eu vou mostrar os
menus que eu criei usando o &lt;a class="reference external" href="https://github.com/davatorium/rofi"&gt;rofi&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Então, o que é o &lt;a class="reference external" href="https://github.com/davatorium/rofi"&gt;rofi&lt;/a&gt;? É basicamente um programa para o qual você fornece …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Outro artigo falando sobre algo que eu configurei no meu ambiente de trabalho
alguns anos atrás e que continuo usando até hoje 🙂. Dessa vez eu vou mostrar os
menus que eu criei usando o &lt;a class="reference external" href="https://github.com/davatorium/rofi"&gt;rofi&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Então, o que é o &lt;a class="reference external" href="https://github.com/davatorium/rofi"&gt;rofi&lt;/a&gt;? É basicamente um programa para o qual você fornece uma
lista de opções, e ele mostra uma janela onde o usuário pode filtrar e escolher
uma opção. Então basicamente pode-se dizer que é um menu de seleção simples mas
universal.&lt;/p&gt;
&lt;p&gt;O rofi também tem alguns menus padrões, como um para abrir aplicações e outro
para alternar entre janelas, que provavelmente são o seu uso mais comum. Eu
também uso esses menus, mas como eles não são algo que eu mesmo customizei, eu
não vou falar sobre eles.&lt;/p&gt;
&lt;div class="section" id="meus-menus"&gt;
&lt;h2&gt;Meus menus&lt;/h2&gt;
&lt;p&gt;Os menus que eu criei usando o rofi são: power, screenshot, unicode e music.&lt;/p&gt;
&lt;div class="section" id="power"&gt;
&lt;h3&gt;Power&lt;/h3&gt;
&lt;img alt="{image}/power.png" src="/images/rofi/power.png" /&gt;
&lt;p&gt;Esse é provavelmente o menu mais importante de se ter quando seu ambiente de
trabalho não oferece o próprio menu (por exemplo, no meu caso eu uso sway que
é apenas um gerenciador de janelas e não tem nenhum menu próprio). Esse menu
fica mapeado em &lt;code class="docutils literal"&gt;Super+Shift+P&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;As opções são auto-explicativas. Elas são ordenadas de menos &amp;quot;danosa&amp;quot; no topo
até mais &amp;quot;danosa&amp;quot; em baixo, para que eu não desligue e perca meu trabalho
acidentalmente quando eu apenas quero bloquear a tela, por exemplo, já que não
tem nenhum pop-up de confirmação.&lt;/p&gt;
&lt;p&gt;Já que &lt;code class="docutils literal"&gt;lock&lt;/code&gt; é a primeira opção, simplesmente abrir o menu e apertar
&lt;code class="docutils literal"&gt;Enter&lt;/code&gt; já bloqueia a tela. A outra opção que eu mais uso é &lt;code class="docutils literal"&gt;hibernate&lt;/code&gt;, que
apenas requer pressionar &lt;code class="docutils literal"&gt;h&lt;/code&gt; para ser selecionada, seguido de &lt;code class="docutils literal"&gt;Enter&lt;/code&gt; para
hibernar. Nos casos raros que eu realmente preciso desligar eu uso &lt;code class="docutils literal"&gt;w&lt;/code&gt;, e para
reiniciar, eu uso &lt;code class="docutils literal"&gt;re&lt;/code&gt;. Claro que eu sempre posso circular pelas opções usando
&lt;code class="docutils literal"&gt;Ctrl+N&lt;/code&gt; e &lt;code class="docutils literal"&gt;Ctrl+P&lt;/code&gt; também, mas as letras geralmente são mais rápidas.&lt;/p&gt;
&lt;p&gt;O motivo de ter muito espaço entre cada ícone e o texto é que cada ícone tem uma
largura diferente, então para que o texto de todas as entradas fique alinhado,
eu precisei colocar um caractere de tab depois de cada ícone. O mesmo é feito
nos outros menus.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="screenshot"&gt;
&lt;h3&gt;Screenshot&lt;/h3&gt;
&lt;img alt="{image}/screenshot.png" src="/images/rofi/screenshot.png" /&gt;
&lt;p&gt;O menu screenshot fica mapeado em &lt;code class="docutils literal"&gt;Super+Shift+S&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;A opção &lt;code class="docutils literal"&gt;screen&lt;/code&gt; tira uma foto da tela inteira (incluindo monitores externos).
Ela chama o &lt;a class="reference external" href="https://github.com/emersion/grim"&gt;grim&lt;/a&gt; por baixo dos panos para tirar as fotos.&lt;/p&gt;
&lt;p&gt;&lt;code class="docutils literal"&gt;region&lt;/code&gt; disponibiliza um cursor que me permite selecionar a região retangular
que será capturada na foto. Isso é feito chamando o &lt;a class="reference external" href="https://github.com/emersion/slurp"&gt;slurp&lt;/a&gt;, que obtém a seleção
do usuário, e fornece as coordenadas resultantes para o &lt;code class="docutils literal"&gt;grim&lt;/code&gt; através do seu
parâmetro &lt;code class="docutils literal"&gt;-g&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code class="docutils literal"&gt;window&lt;/code&gt; também disponibiliza um cursor, mas ele é usado para selecionar uma
única janela que será capturada. Isso é feito extraindo as dimensões das
janelas do sway e as passando como opções para o &lt;code class="docutils literal"&gt;slurp&lt;/code&gt; que então permite que
apenas um desses retângulos seja selecionado. O comando que faz isso, disponível
no README do slurp, é:&lt;/p&gt;
&lt;pre class="code literal-block"&gt;
swaymsg -t get_tree | jq -r '.. | select(.pid? and .visible?) | .rect | \&amp;quot;\(.x),\(.y) \(.width)x\(.height)\&amp;quot;' | slurp
&lt;/pre&gt;
&lt;p&gt;&lt;code class="docutils literal"&gt;colorpicker&lt;/code&gt; disponibiliza um cursor também, mas não tira nenhuma foto, o que
ele faz é copiar o valor RGB do pixel que for clicado para o &lt;em&gt;clipboard&lt;/em&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="unicode"&gt;
&lt;h3&gt;Unicode&lt;/h3&gt;
&lt;img alt="{image}/unicode.png" src="/images/rofi/unicode.png" /&gt;
&lt;p&gt;O menu unicode fica mapeado em &lt;code class="docutils literal"&gt;Super+Shift+U&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;O propósito desse menu é facilitar o acesso a caracteres que eu normalmente não
tenho mapeado no meu teclado. Ele mostra cada caractere e seu nome. Já que há
muitos caracteres, eu uso duas colunas no rofi para conseguir ver mais ao mesmo
tempo.&lt;/p&gt;
&lt;p&gt;Para usar esse menu eu escrevo o nome do caractere que eu quero e, com ele
selecionado, pressiono &lt;code class="docutils literal"&gt;Enter&lt;/code&gt; para copiá-lo para o &lt;em&gt;clipboard&lt;/em&gt;. Então eu
posso colá-lo onde eu estava precisando.&lt;/p&gt;
&lt;p&gt;Um exemplo de uso é para pegar os caracteres ordinais usados em português: ª e º
(chamados Indicador Ordinal Feminino e Masculino, respectivamente). Esses
caracteres estão mapeados no layout português do teclado, mas para mim é mais
fácil encontrar buscando no menu do que achar no teclado.&lt;/p&gt;
&lt;p&gt;Esse menu é gerado em python usando a função &lt;code class="docutils literal"&gt;unicodedata.name()&lt;/code&gt; para obter o
nome Unicode para cada caractere e &lt;code class="docutils literal"&gt;chr()&lt;/code&gt; para obter o caractere em si.
&lt;code class="docutils literal"&gt;wl-copy&lt;/code&gt; é usado para copiar o caractere para o &lt;em&gt;clipboard&lt;/em&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="music"&gt;
&lt;h3&gt;Music&lt;/h3&gt;
&lt;img alt="{image}/music.png" src="/images/rofi/music.png" /&gt;
&lt;p&gt;O menu de música fica mapeado em &lt;code class="docutils literal"&gt;Super+Shift+M&lt;/code&gt; e é o meu favorito! Cada
opção abre seu próprio sub-menu. Ele tem tudo que eu preciso para sempre poder
rapidamente tocar a música que eu quero.&lt;/p&gt;
&lt;p&gt;Um detalhe legal é que se uma música já está tocando, o que você escolher vai
tocar apenas depois que a música atual terminar. Caso contrário, se nenhuma
música está tocando, a escolha é tocada imediatamente. Desse jeito, a música
atual nunca é parada no meio, o que é ótimo.&lt;/p&gt;
&lt;p&gt;Além disso, diferentemente dos outros menus, os sub-menus de música permitem
(quando fizer sentido) que múltiplas opções sejam selecionadas usando
&lt;code class="docutils literal"&gt;Shift+Enter&lt;/code&gt; ao invés de &lt;code class="docutils literal"&gt;Enter&lt;/code&gt;, apesar de eu raramente usar isso.&lt;/p&gt;
&lt;p&gt;Vamos ver cada um dos sub-menus.&lt;/p&gt;
&lt;div class="section" id="playlist"&gt;
&lt;h4&gt;Playlist&lt;/h4&gt;
&lt;img alt="{image}/music-playlist.png" src="/images/rofi/music-playlist.png" /&gt;
&lt;p&gt;O menu playlist me permite tocar qualquer uma das minhas listas de reprodução.
A randomização é ligada automaticamente quando eu seleciono uma lista de
reprodução, para que a ordem das músicas na lista não seja sempre a mesma.&lt;/p&gt;
&lt;p&gt;A lista de reprodução que eu mais ouço, &lt;code class="docutils literal"&gt;Saved&lt;/code&gt;, é a primeira propositalmente.
Assim, quando eu simplesmente quero escutar &lt;em&gt;qualquer coisa&lt;/em&gt;, basta apertar
&lt;code class="docutils literal"&gt;Super+Shift+M&lt;/code&gt;, &lt;code class="docutils literal"&gt;Enter&lt;/code&gt;, &lt;code class="docutils literal"&gt;Enter&lt;/code&gt;. Escutar música de fundo quando eu
preciso me concentrar também é bem fácil: &lt;code class="docutils literal"&gt;Super+Shift+M&lt;/code&gt;, &lt;code class="docutils literal"&gt;Enter&lt;/code&gt;,
&lt;code class="docutils literal"&gt;Ctrl+N&lt;/code&gt;, &lt;code class="docutils literal"&gt;Enter&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Caso você esteja curioso sobre como as listas de reprodução são criadas, eu já
escrevi sobre no &lt;a class="reference external" href="/pt-br/post/geracao-de-listas-de-reproducao-de-musica-com-o-mpd.html"&gt;artigo "Geração de listas de reprodução de música com o MPD"&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="song"&gt;
&lt;h4&gt;Song&lt;/h4&gt;
&lt;img alt="{image}/music-song.png" src="/images/rofi/music-song.png" /&gt;
&lt;p&gt;O menu song é para quando eu quero ouvir alguma música específica. Cada entrada
mostra o nome da música, artista e álbum. O rofi não suporta mostrar múltiplas
strings na mesma entrada desse jeito por padrão, então para conseguir isso eu
preciso construir uma string com a mesma largura da janela do rofi, com cada
campo ocupando uma parcela igual.&lt;/p&gt;
&lt;p&gt;Para ter uma largura mais ou menos previsível eu fixo a largura do rofi em
termos de caracteres colocando &lt;code class="docutils literal"&gt;width: -100;&lt;/code&gt; no seu config. Então eu
disponibilizo esse valor dentro do script python simplesmente lendo o valor
desse arquivo de config (nada elegante, mas funciona). Por fim eu calculo a
largura de cada campo, e adiciono espaçamento e/ou trunco cada um dos campos
individualmente para mantê-los na largura desejada.&lt;/p&gt;
&lt;p&gt;Já que a fonte que eu uso no rofi não é monoespaçada, a largura que eu configuro
no config é apenas um estimativa e não é precisa de forma alguma. Então pode ter
um pouco de folga, mas funciona bem o suficiente.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="queue"&gt;
&lt;h4&gt;Queue&lt;/h4&gt;
&lt;img alt="{image}/music-queue.png" src="/images/rofi/music-queue.png" /&gt;
&lt;p&gt;O menu queue mostra a fila de músicas atual que está tocando. Eu uso ele quando
quero ver as próximas músicas que vão tocar ou tocar uma outra música da fila
atual.&lt;/p&gt;
&lt;p&gt;Quando esse menu é aberto o cursor começa já selecionando a música que está
tocando no momento.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="album"&gt;
&lt;h4&gt;Album&lt;/h4&gt;
&lt;img alt="{image}/music-album.png" src="/images/rofi/music-album.png" /&gt;
&lt;p&gt;O menu album me permite tocar um álbum inteiro. Uma diferença importante dos
outros menus é que tocar um álbum desliga a randomização, para que as músicas do
álbum sejam tocadas na ordem correta.&lt;/p&gt;
&lt;p&gt;Cada entrada mostra o nome do álbum e do artista. Na frente também tem um &lt;code class="docutils literal"&gt;%&lt;/code&gt;
para marcar o que eu chamo de &amp;quot;álbum completo&amp;quot;. O critério é que quando eu gosto
de pelo menos 80% das músicas de um álbum, eu marco ele com um &lt;code class="docutils literal"&gt;%&lt;/code&gt;, e passo a
escutá-lo por completo, mesmo as músicas que eu não gostei tanto. Por outro
lado, se eu gosto de menos de 80% das músicas no álbum, eu só ouço as músicas
que eu gostei.&lt;/p&gt;
&lt;p&gt;A ideia por trás desse conceito de &amp;quot;álbum completo&amp;quot; é que eu acho que existe um
certo valor em escutar um álbum por completo, mas ao mesmo tempo não acho que
faz sentido ouvir um álbum completo quando você não gosta de muitas das músicas.
Então eu inventei essa folga de 20% para que os álbuns não precisem ser
perfeitos para que eu esteja disposto a escutá-los por completo.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="artist"&gt;
&lt;h4&gt;Artist&lt;/h4&gt;
&lt;img alt="{image}/music-artist.png" src="/images/rofi/music-artist.png" /&gt;
&lt;p&gt;O menu artist é o que eu uso para tocar todas as músicas e álbuns de um único
artista, em uma ordem qualquer.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="current"&gt;
&lt;h4&gt;Current&lt;/h4&gt;
&lt;img alt="{image}/music-current.png" src="/images/rofi/music-current.png" /&gt;
&lt;p&gt;O menu current é uma adição bem nova! Eu percebi que às vezes estou ouvindo uma
lista de reprodução e uma música toca que me faz querer ouvir o álbum dela
inteiro ou todas as músicas desse artista. Esse menu me permite fazer exatamente
isso.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="options"&gt;
&lt;h4&gt;Options&lt;/h4&gt;
&lt;img alt="{image}/music-options.png" src="/images/rofi/music-options.png" /&gt;
&lt;p&gt;O menu options dá acesso a mais algumas funcionalidades extras. &lt;code class="docutils literal"&gt;update&lt;/code&gt; faz o
MPD atualizar seu banco de dados de música, o que às vezes é útil se acabei de
editar um arquivo. &lt;code class="docutils literal"&gt;random&lt;/code&gt; liga ou desliga a randomização de músicas. Eu
raramente uso isso já que meus menus já são configurados para ligar ou desligar
a randomização com base no contexto. &lt;code class="docutils literal"&gt;update playlists&lt;/code&gt; roda meu script de
geração de listas de reprodução para que elas sejam atualizadas.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="outras-consideracoes"&gt;
&lt;h2&gt;Outras considerações&lt;/h2&gt;
&lt;p&gt;Esses são meus menus e o que eu queria compartilhar nesse artigo, mas vale
mencionar alguns outros pontos.&lt;/p&gt;
&lt;p&gt;Primeiro, tem um outro menu rofi que eu uso e gosto, mas ele não é um que eu
mesmo criei. Ele se chama &lt;a class="reference external" href="https://github.com/fdw/rofimoji"&gt;rofimoji&lt;/a&gt; e permite que você muito facilmente pesquise
e copie emojis.&lt;/p&gt;
&lt;p&gt;Em segundo lugar, vale mencionar que o rofi oficialmente só funciona no X11,
então eu na verdade uso o &lt;a class="reference external" href="https://aur.archlinux.org/packages/rofi-lbonn-wayland-git/"&gt;fork do rofi para wayland&lt;/a&gt;, como eu mencionei no
&lt;a class="reference external" href="/pt-br/post/mudando-para-o-wayland.html"&gt;artigo "Mudando para o Wayland"&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;E finalmente, para tornar a criação de menus rofi mais fácil nos meus scripts em
python, eu abstraí a execução do rofi em uma função python com o seguinte
protótipo:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;multi&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[]):&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;onde &lt;code class="docutils literal"&gt;prompt&lt;/code&gt; é a string que aparece no prompt, &lt;code class="docutils literal"&gt;options&lt;/code&gt; é a lista de
opções que podem ser escolhidas, &lt;code class="docutils literal"&gt;multi&lt;/code&gt; representa se múltiplas opções podem
ser escolhidas ou não, e &lt;code class="docutils literal"&gt;args&lt;/code&gt; permite passar argumentos adicionais ao rofi.
O retorno da função é a/as opção/opções que foi/foram selecionada(s) (apenas a
opção se &lt;code class="docutils literal"&gt;multi&lt;/code&gt; era falso, ou a lista de opções se era verdadeiro). Se a
seleção foi abortada uma exceção é lançada. Eu considerei usar o &lt;a class="reference external" href="https://github.com/bcbnz/python-rofi"&gt;python-rofi&lt;/a&gt; ao
invés de criar meu próprio módulo, mas esse módulo retorna o índice do que foi
escolhido ao invés da string em si, o que apenas tornaria o uso mais complicado.
Já que era bem fácil criar meu próprio módulo, foi o que eu fiz.&lt;/p&gt;
&lt;p&gt;E é isso. Eu realmente gosto do quão fácil é criar menus usando o rofi em python
🙂.&lt;/p&gt;
&lt;/div&gt;
</content><category term="2021"/><category term="rofi"/><category term="desktop"/></entry><entry><title>Os blocos na minha barra de status</title><link href="https://nfraprado.net/pt-br/post/os-blocos-na-minha-barra-de-status.html" rel="alternate"/><published>2021-11-26T00:00:00-03:00</published><updated>2021-11-26T00:00:00-03:00</updated><author><name>Nícolas F. R. A. Prado</name></author><id>tag:nfraprado.net,2021-11-26:/pt-br/post/os-blocos-na-minha-barra-de-status.html</id><summary type="html">&lt;p&gt;Cinco anos atrás quando eu mudei para o gerenciador de janelas i3, eu comecei a
usar a barra de status dele, o i3bar. Ele é baseado em texto e depende de você o
que é mostrado lá. Mas ele não é muito modular: é estranho combinar informações
diferentes já que …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Cinco anos atrás quando eu mudei para o gerenciador de janelas i3, eu comecei a
usar a barra de status dele, o i3bar. Ele é baseado em texto e depende de você o
que é mostrado lá. Mas ele não é muito modular: é estranho combinar informações
diferentes já que tudo precisa ser juntado em uma única string manualmente.&lt;/p&gt;
&lt;p&gt;Mais ou menos um ano depois eu descobri um programa que resolve esse problema:
&lt;a class="reference external" href="https://github.com/vivien/i3blocks"&gt;i3blocks&lt;/a&gt;. O funcionamento dele é baseado em um config onde você define os
blocos que você quer e qual o script que vai ser rodado para cada um. A saída de
texto de cada script é o que vai ser mostrado para esse bloco na barra de
status.&lt;/p&gt;
&lt;div class="section" id="meus-blocos"&gt;
&lt;h2&gt;Meus blocos&lt;/h2&gt;
&lt;p&gt;Eu atualmente tenho 9 blocos na minha barra de status: hora, rotina, tarefa,
bateria, teclado, armazenamento-root, armazenamento-home, atualização e música.&lt;/p&gt;
&lt;div class="section" id="hora"&gt;
&lt;h3&gt;Hora&lt;/h3&gt;
&lt;img alt="{image}/time.png" src="/images/i3blocks/time.png" /&gt;
&lt;p&gt;Provavelmente o bloco mais óbvio. Eu não consigo pensar em nenhuma barra de
status que não mostre a hora atual...&lt;/p&gt;
&lt;p&gt;Esse bloco mostra a data (dia da semana, dia, mês e ano) e hora. Mostrar a hora
em negrito é um detalhe que eu gosto bastante, mas não lembro de onde eu peguei
a ideia.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="rotina"&gt;
&lt;h3&gt;Rotina&lt;/h3&gt;
&lt;img alt="{image}/schedule.png" src="/images/i3blocks/schedule.png" /&gt;
&lt;p&gt;Esse bloco me mostra a rotina atual, ou seja, o que eu deveria estar fazendo
agora. Como você pode ver agora eu deveria estar lendo um livro ao invés de
escrever esse artigo para o blog... Mas eu preciso correr se quiser publicar
ele! 😝&lt;/p&gt;
&lt;p&gt;Eu já mostrei esse bloco anteriormente no &lt;a class="reference external" href="/pt-br/post/organizacao-alem-do-taskwarrior.html"&gt;artigo "Organização além do Taskwarrior"&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="tarefa"&gt;
&lt;h3&gt;Tarefa&lt;/h3&gt;
&lt;img alt="{image}/task.png" src="/images/i3blocks/task.png" /&gt;
&lt;p&gt;Esse bloco mostra o contexto de tarefas atual e o número de tarefas que eu tenho
na minha caixa de entrada, o número de projetos empacados e o número de tarefas
com prazos próximos.&lt;/p&gt;
&lt;p&gt;Quando todos os três são zero, esse bloco é escondido, apesar de isso ser bem
raro. Como você pode ver, eu não tenho tido tempo ultimamente para organizar as
tarefas da minha caixa de entrada 😅.&lt;/p&gt;
&lt;p&gt;Eu também já mostrei esse bloco no artigo &lt;a class="reference external" href="/pt-br/post/organizacao-alem-do-taskwarrior.html"&gt;artigo "Organização além do Taskwarrior"&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="bateria"&gt;
&lt;h3&gt;Bateria&lt;/h3&gt;
&lt;img alt="{image}/battery.png" src="/images/i3blocks/battery.png" /&gt;
&lt;p&gt;Esse bloco mostra a porcentagem atual de carga da bateria. O ícone reflete a
carga atual (dentre 4 possibilidades), e quando a bateria está baixa o fundo fica
vermelho para chamar minha atenção.&lt;/p&gt;
&lt;p&gt;Quando a bateria está carregando e com uma carga alta, esse bloco fica oculto.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="teclado"&gt;
&lt;h3&gt;Teclado&lt;/h3&gt;
&lt;img alt="{image}/keyboard.png" src="/images/i3blocks/keyboard.png" /&gt;
&lt;p&gt;Esse bloco me mostra o layout atual do teclado. Já que eu só uso dois layouts,
pt-br (ABNT2) e en-us, e na maior parte do tempo eu uso o inglês, esse bloco só
mostra em vermelho quando eu estou no layout português. Quando estou no inglês,
ele fica oculto.&lt;/p&gt;
&lt;p&gt;Eu adicionei esse bloco depois de tentar usar o vim várias vezes tendo esquecido
que estava com o layout português.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="armazenamento"&gt;
&lt;h3&gt;Armazenamento&lt;/h3&gt;
&lt;img alt="{image}/storage-root.png" src="/images/i3blocks/storage-root.png" /&gt;
&lt;img alt="{image}/storage-home.png" src="/images/i3blocks/storage-home.png" /&gt;
&lt;p&gt;Esses dois blocos mostram o espaço de armazenamento disponível e total nos meus
discos. O primeiro (ícone de computador) mostra para a partição root, enquanto o
segundo (ícone de casa) mostra para a partição home. Esses blocos ficam quase
sempre escondidos, a não ser que o espaço disponível fique baixo. Nesse caso
eles aparecem com o fundo vermelho.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Obs&lt;/strong&gt;: O espaço livre mostrado nessas fotos normalmente não seria considerado
baixo, eu que forcei eles a aparecerem.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="atualizacao"&gt;
&lt;h3&gt;Atualização&lt;/h3&gt;
&lt;img alt="{image}/update.png" src="/images/i3blocks/update.png" /&gt;
&lt;p&gt;Esse bloco me mostra o número de pacotes que precisam ser atualizados. Ele
apenas aparece se o número for alto o suficiente, caso contrário fica oculto.&lt;/p&gt;
&lt;p&gt;Antigamente eu fazia com o que o fundo desse bloco ficasse vermelho se o pacote
do kernel precisasse ser atualizado, já que eu percebi que no Arch Linux
atualizar o kernel não mantém os módulos da versão anterior. Isso significa que
sempre após atualizar o kernel o sistema deve ser reiniciado, caso contrário
alguns bugs estranhos podem acontecer devido à falta de módulos necessários (por
exemplo, USBs não funcionarem). Porém depois de descobrir o pacote
&lt;a class="reference external" href="https://archlinux.org/packages/community/any/kernel-modules-hook/"&gt;kernel-modules-hook&lt;/a&gt; que resolve esse problema, eu removi essa checagem e nem me
preocupo mais na hora de atualizar o kernel.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="musica"&gt;
&lt;h3&gt;Música&lt;/h3&gt;
&lt;img alt="{image}/music.png" src="/images/i3blocks/music.png" /&gt;
&lt;p&gt;Esse bloco me mostra a música que está sendo tocada atualmente. Ele mostra o
artista seguido do nome da música, e o ícone reflete o estado atual: tocando ou
pausado. Quando nenhuma música está tocando ele fica oculto.&lt;/p&gt;
&lt;p&gt;Como nome de banda ou música podem ficar grandes, a partir de um certo tamanho
eu omito o restante usando um &amp;quot;...&amp;quot;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="bloco-antigo-cardapio-unicamp"&gt;
&lt;h3&gt;Bloco antigo: cardapio-unicamp&lt;/h3&gt;
&lt;img alt="{image}/cardapio-unicamp.png" src="/images/i3blocks/cardapio-unicamp.png" /&gt;
&lt;img alt="{image}/cardapio-unicamp-semana.png" src="/images/i3blocks/cardapio-unicamp-semana.png" /&gt;
&lt;p&gt;Esse não é um bloco que eu uso atualmente, mas era meu favorito então eu quero
mencioná-lo. Seu propósito era me informar qual o cardápio do bandejão da
faculdade, para que eu pudesse decidir se queria comer lá ou em outro lugar.&lt;/p&gt;
&lt;p&gt;Houveram duas iterações. No começo, eu simplesmente mostrava a descrição do
prato principal da próxima refeição. Depois de um tempo eu mudei ele para me
mostrar a qualidade das refeições para a semana inteira. Eu fiz isso comparando
a descrição do prato com palavras que eu considerava bom ou ruim. O resultado
final eram dez quadrados, cada dia separado por um &lt;code class="docutils literal"&gt;|&lt;/code&gt; e em cada um o primeiro
quadrado mostrava almoço e o segundo, a janta. A cor dos quadrados mostrava a
qualidade: branco para normal, verde para bom e vermelho (ou magenta nesse
esquema de cores antigo) para ruim.&lt;/p&gt;
&lt;p&gt;Os cardápios eram obtidos usando um programa em python que eu escrevi chamado
&lt;a class="reference external" href="https://codeberg.org/nfraprado/cardapio-unicamp"&gt;cardapio-unicamp&lt;/a&gt;. O script para obter a qualidade dos pratos para a semana
inteira &lt;a class="reference external" href="https://codeberg.org/nfraprado/cardapio-unicamp/src/branch/master/exemplos/cardapio_ru_semana.py"&gt;também está disponível nesse repositório&lt;/a&gt;. A conversão de &lt;code class="docutils literal"&gt;|&lt;/code&gt;,
&lt;code class="docutils literal"&gt;+&lt;/code&gt; e &lt;code class="docutils literal"&gt;-&lt;/code&gt; para os quadrados coloridos eu não tenho publicada, mas eram
algumas simples (mas feias) linhas de bash, então eu tenho certeza que você
consegue fazer algo melhor que isso se quiser 🙂.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="melhorias"&gt;
&lt;h2&gt;Melhorias&lt;/h2&gt;
&lt;p&gt;Eu realmente gosto da configuração atual da minha barra de status, mas levou
tempo e melhorias para chegar aqui.&lt;/p&gt;
&lt;p&gt;Por exemplo eu sinto que consistência é importante para que seja fácil de
identificar cada bloco: cada um tem sua própria cor acima e um pouco de espaço
em volta, e seu ícone sempre é a primeira coisa à esquerda, seguido do texto.&lt;/p&gt;
&lt;p&gt;Eu também aproveito o uso de cores para reduzir a quantidade de texto usada nos
blocos. Por exemplo, o bloco de tarefa possui três números diferentes, mas eles
não precisam de legenda, já que cada cor já deixa evidente seu significado.&lt;/p&gt;
&lt;p&gt;Reduzir a quantidade de coisa na barra de status me ajudou a focar no que é
realmente relevante no momento. Para isso eu comecei a me questionar quais
informações realmente eram importantes. Eu não me importo em qual rede wifi eu
estou conectado na maior parte do tempo, mas eu tinha um bloco para isso (e é um
bem comum), então eu só removi ele. E tem informações que só são relevantes
às vezes, e é por isso que agora meus blocos ficam ocultos a não ser que a
informação atual deles seja relevante.&lt;/p&gt;
&lt;p&gt;Eu também reduzi o número de atualizações inúteis dos blocos fazendo com que
sempre que possível eles atualizassem com base em sinais ao invés de tempo.
Então por exemplo o bloco de teclado apenas atualiza quando eu aperto o atalho
de mudança do teclado, que manda um sinal para esse bloco.&lt;/p&gt;
&lt;p&gt;Ligar cada bloco a um sinal também faz com que os blocos atualizem mais rápidos
para eventos assíncronos. Por exemplo, eu atualizo os pacotes no meu computador
usando um comando, que além de atualizar usando o pacman, manda um sinal para
o bloco de atualização no final. Então assim que eu acabo de atualizar os
pacotes, esse bloco desaparece, e não depois de um certo tempo.&lt;/p&gt;
&lt;p&gt;Com o passar do tempo eu também escrevi um pouco de código para deixar as
definições de blocos mais fáceis. Como outras partes do meu sistema, eu comecei
meus blocos em bash, mas eventualmente migrei eles para python. Como parte disso
eu escrevi uma função &lt;code class="docutils literal"&gt;print_i3blocks&lt;/code&gt; em python que recebe o ícone, texto e
cor para o bloco e escreve eles na saída no formato JSON necessário para o
i3blocks.&lt;/p&gt;
&lt;p&gt;Eu também tenho um comando &lt;code class="docutils literal"&gt;update_i3blocks&lt;/code&gt;  que recebe o nome do bloco e
envia o sinal correspondente, assim eu posso atualizar um bloco específico a
partir de outros comandos no sistema.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="o-bug-irritante"&gt;
&lt;h2&gt;O bug irritante&lt;/h2&gt;
&lt;p&gt;Quando eu migrei os scripts dos blocos para python eu comecei a ter problemas
com os blocos desaparecendo aleatoriamente. Levou quase um ano para realmente
entrar no código do i3blocks e encontrar e &lt;a class="reference external" href="https://github.com/vivien/i3blocks/pull/454"&gt;consertar esse problema&lt;/a&gt;. Apesar
de ter sido difícil de achar, foi muito interessante entender esse problema. Não
tinha nada a ver com o python, é só que como código em python roda mais devagar
que bash, ele ficou mais aparente.&lt;/p&gt;
&lt;p&gt;Infelizmente, já que o repositório do i3blocks não está mais ativamente mantido,
se você estiver tendo esse mesmo problema e quiser consertá-lo, vai precisar
aplicar o patch e compilar por si mesmo (apesar de ser bem simples). De qualquer
forma eu não consigo nem dizer o quão feliz estou de finalmente ter me livrado
desse bug. Agora eu posso simplesmente relaxar e aproveitar minha barra de
status mostrando todas, e apenas, as informações que são relevantes para mim 🙂.&lt;/p&gt;
&lt;/div&gt;
</content><category term="2021"/><category term="i3blocks"/><category term="desktop"/></entry><entry><title>Revivendo um computador no meio do nada</title><link href="https://nfraprado.net/pt-br/post/revivendo-um-computador-no-meio-do-nada.html" rel="alternate"/><published>2021-10-23T00:00:00-03:00</published><updated>2021-10-23T00:00:00-03:00</updated><author><name>Nícolas F. R. A. Prado</name></author><id>tag:nfraprado.net,2021-10-23:/pt-br/post/revivendo-um-computador-no-meio-do-nada.html</id><summary type="html">&lt;p&gt;No começo desse mês, eu estava passando umas semanas em outro país. Era tarde e
eu estava mais uma vez olhando meus arquivos pessoais e pensando se havia uma
forma melhor de organizá-los em pastas.&lt;/p&gt;
&lt;p&gt;Depois de pensar um pouco, eu decidi usar uma nova estrutura e movi alguns
arquivos …&lt;/p&gt;</summary><content type="html">&lt;p&gt;No começo desse mês, eu estava passando umas semanas em outro país. Era tarde e
eu estava mais uma vez olhando meus arquivos pessoais e pensando se havia uma
forma melhor de organizá-los em pastas.&lt;/p&gt;
&lt;p&gt;Depois de pensar um pouco, eu decidi usar uma nova estrutura e movi alguns
arquivos. No meio tempo, eu percebi que eu não tinha atualizado meus pacotes já
há algum tempo, então eu comecei uma atualização.&lt;/p&gt;
&lt;p&gt;No final da atualização eu vi um erro. O &lt;code class="docutils literal"&gt;/etc/mkinitcpio.conf&lt;/code&gt; não tinha sido
encontrado. Meu &lt;code class="docutils literal"&gt;/etc/mkinitcpio.conf&lt;/code&gt; é um link simbólico para um arquivo na
minha home já que isso torna mais fácil de acompanhar as mudanças nesse config
como parte dos meus backups. E entre os arquivos que eu tinha acabado de mover
estava justamente esse config, então eu atualizei o link para apontar para a
nova localização. Eu rodei a atualização de novo e o &lt;code class="docutils literal"&gt;pacman&lt;/code&gt; reportou
&amp;quot;nothing to be done&amp;quot;. Eu inocentemente acreditei que estava tudo certo agora.&lt;/p&gt;
&lt;p&gt;Então eu reiniciei para checar que estava tudo funcionando com os arquivos no
novo lugar. Assim que eu selecionei meu sistema no GRUB, ele reportou que o
&lt;code class="docutils literal"&gt;/initramfs-linux.img&lt;/code&gt; estava faltando. Mesma coisa para o initramfs de
reserva (&lt;em&gt;fallback&lt;/em&gt;). Eu estava oficialmente trancado para fora do meu sistema.&lt;/p&gt;
&lt;p&gt;Agora tinha ficado claro o que aconteceu. Durante a atualização o kernel foi
atualizado, e como parte disso a initramfs foi re-gerada, mas já que o arquivo
de configuração estava inválido, isso foi abortado. Quando eu rodei o comando de
atualização novamente, o &lt;code class="docutils literal"&gt;pacman&lt;/code&gt; concluiu que nada mais precisava ser
atualizado e assim disse. O que eu deveria ter feito é rodar manualmente o
comando de instalação do pacote do kernel novamente para que a initramfs fosse
de fato gerada. Como eu não fiz isso, não havia mais nenhum arquivo de initramfs
na minha partição de boot, e portanto eu não conseguia inicializar o sistema.&lt;/p&gt;
&lt;p&gt;Eu respirei fundo e comecei a pensar o que eu podia fazer. Eu só precisava de um
pendrive com a ISO do ArchLinux para bootar dele e rodar de novo a instalação do
pacote de kernel para arrumar essa bagunça. Mas já que eu estava em outro país,
meus equipamentos eram limitados. Felizmente eu tinha um pendrive, mas não tinha
outro computador para gravar a ISO.&lt;/p&gt;
&lt;p&gt;A não ser... A não ser que eu pudesse usado meu amado celular Nexus 5X para
gravar. A porta de carregamento dele é USB tipo C, e eu suspeitava que ela
também funcionasse como OTG. Então eu tinha um pendrive e um cabo para conversão
de USB A para C, só restava descobrir se era de fato possível gravar uma ISO do
celular.&lt;/p&gt;
&lt;p&gt;Eu imediatamente abri a F-Droid e pesquisei por um gravador de ISO. Para minha
felicidade encontrei o &lt;a class="reference external" href="https://f-droid.org/en/packages/eu.depau.etchdroid/"&gt;EtchDroid&lt;/a&gt;. Sua descrição realmente ressoou comigo:
&amp;quot;Você pode usá-lo para criar um pendrive bootável com GNU/Linux quando seu
laptop está morto e você está no meio do nada.&amp;quot; (tradução minha). Percebendo que
talvez realmente fosse possível, eu fui na página do Arch Linux e baixei a ISO.&lt;/p&gt;
&lt;p&gt;Eu então instalei o EtchDroid e abri ele. Eu selecionei a opção de gravar uma
ISO, selecionei o arquivo da ISO, conectei o pendrive no celular e selecionei
ele no aplicativo. Eu realmente gostei da interface simples dele:&lt;/p&gt;
&lt;img alt="{image}/etchdroid.png" src="/images/etchdroid/etchdroid.png" /&gt;
&lt;p&gt;Eu apertei o botão de iniciar a gravação e uma notificação apareceu mostrando o
progresso:&lt;/p&gt;
&lt;img alt="{image}/flash_in_progress.png" src="/images/etchdroid/flash_in_progress.png" /&gt;
&lt;p&gt;E depois de alguns segundos, tinha acabado:&lt;/p&gt;
&lt;img alt="{image}/flash_done.png" src="/images/etchdroid/flash_done.png" /&gt;
&lt;p&gt;Eu então desconectei o pendrive do celular e conectei no meu notebook. Eu fiquei
muito feliz de ver que a imagem USB tinha funcionado e bootei dela.&lt;/p&gt;
&lt;p&gt;Para consertar o problema eu configurei meus &lt;em&gt;mountpoints&lt;/em&gt;, fiz um &lt;em&gt;chroot&lt;/em&gt; para
dentro do sistema, e rodei &lt;code class="docutils literal"&gt;pacman -S linux&lt;/code&gt; para reinstalar o pacote do
kernel e a initramfs ser re-gerada.&lt;/p&gt;
&lt;p&gt;Com a initramfs de volta, eu reiniciei e tudo funcionou perfeitamente de novo
🙂.&lt;/p&gt;
&lt;p&gt;Eu estou muito feliz que apesar do meu erro besta, eu consegui resolvê-lo bem
rápido mesmo em um ambiente limitado graças a esse excelente aplicativo chamado
EtchDroid. Com certeza vou guardar ele para futuras emergências.&lt;/p&gt;
</content><category term="2021"/><category term="android"/><category term="linux"/></entry><entry><title>Minha jornada até um bom sistema de backup</title><link href="https://nfraprado.net/pt-br/post/minha-jornada-ate-um-bom-sistema-de-backup.html" rel="alternate"/><published>2021-09-27T00:00:00-03:00</published><updated>2021-09-27T00:00:00-03:00</updated><author><name>Nícolas F. R. A. Prado</name></author><id>tag:nfraprado.net,2021-09-27:/pt-br/post/minha-jornada-ate-um-bom-sistema-de-backup.html</id><summary type="html">&lt;p&gt;Eu confesso que sou um pouco acumulador de dados. Eu ainda tenho alguns dos
primeiros programas que escrevi, fotos que tirei durante viagens e desenhos que
fiz muitos anos atrás, para citar alguns exemplos. E já que eu não confio em
alguma empresa para armazenar todos esses dados para mim …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Eu confesso que sou um pouco acumulador de dados. Eu ainda tenho alguns dos
primeiros programas que escrevi, fotos que tirei durante viagens e desenhos que
fiz muitos anos atrás, para citar alguns exemplos. E já que eu não confio em
alguma empresa para armazenar todos esses dados para mim, já era bem claro que
eu precisava fazer meus próprios backups.&lt;/p&gt;
&lt;p&gt;Uma lição que eu aprendi cedo lendo online é que backups devem ser o mais fácil
possível de fazer, preferencialmente totalmente automáticos, se não você acaba
nem fazendo eles.&lt;/p&gt;
&lt;p&gt;Nesse artigo eu quero relembrar meu sistema de backup anterior e então falar
sobre o atual. Provavelmente até o final vai ficar claro que o atual é muito
mais simples e melhor.&lt;/p&gt;
&lt;div class="section" id="antigamente"&gt;
&lt;h2&gt;Antigamente&lt;/h2&gt;
&lt;p&gt;Eu acho que foi em 2018 que eu comecei a pensar seriamente sobre fazer backups.
Para isso eu precisava tanto de hardware e software. O hardware para conter os
backups e o software para fazê-los acontecer.&lt;/p&gt;
&lt;div class="section" id="hardware"&gt;
&lt;h3&gt;Hardware&lt;/h3&gt;
&lt;p&gt;Eu tinha um computador de desktop que não funcionava mais devido a problemas na
placa-mãe, mas todo o restante funcionava bem, então era o candidato perfeito
para usar para o meu sistema de backup. O disco rígido poderia ser usado para
armazenar os backups, a fonte para fornecer energia a tudo, e o gabinete para
abrigar todos os componentes.&lt;/p&gt;
&lt;p&gt;Eu comecei removendo a placa-mãe que não funcionava mais e percebi que a fonte
não fornecia nenhuma tensão por padrão. Pesquisando online pelo seu datasheet eu
descobri quais fios precisavam ser curto-circuitados para que ela ligasse. Já
que o botão de ligar no gabinete era um push button que conectava na placa-mãe,
que por sua vez curto-circuitava esses fios, eu também removi esse botão e
coloquei uma chave liga-desliga no lugar. Eu soldei a chave nos fios da fonte e
grudei ela no gabinete com fita isolante e umas plaquinhas finas de metal que eu
tinha sobrando para dar um pouco de rigidez. Ficou assim:&lt;/p&gt;
&lt;img alt="{image}/button.jpg" src="/images/backup/button.jpg" /&gt;
&lt;p&gt;Quanto ao componente principal, o computador que faria os backups, eu tinha uma
Raspberry Pi que não estava sendo usada então ela era a escolha óbvia. Eu peguei
um conector micro USB de um cabo velho, abri o cabo, e soldei seus fios nos fios
de 5V e terra da fonte. Assim, eu conseguiria energizar tudo pela mesma fonte,
já que o disco rígido já era alimentado por ela.&lt;/p&gt;
&lt;p&gt;Finalmente, já que o disco rígido era interno, e portanto usava conexão SATA
para transferir dados, eu comprei um cabo conversor SATA para USB baratinho e
usei ele para conectar o disco na raspberry. A montagem completa fica assim:&lt;/p&gt;
&lt;img alt="{image}/internals.jpg" src="/images/backup/internals.jpg" /&gt;
&lt;p&gt;(Essa é uma foto atual, na época a raspberry usava WiFi ao invés do cabo
Ethernet)&lt;/p&gt;
&lt;p&gt;E com isso o hardware estava completo, o próximo passo era a solução de software
para fazer os backups acontecerem.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="software"&gt;
&lt;h3&gt;Software&lt;/h3&gt;
&lt;p&gt;Nessa época em que eu estava pensando sobre qual software usar para os meus
backups, eu esbarrei &lt;a class="reference external" href="https://opensource.com/article/18/8/automate-backups-raspberry-pi"&gt;nesse artigo&lt;/a&gt;. Ele me apresentou as ideias de backup
incremental (apenas armazenar as diferenças dos arquivos do backup anterior) e
rotação de backup (apenas armazenar os últimos N backups, deletando os mais
antigos) que eu achei geniais. Ele também se baseava no rsync para as cópias,
que era um programa com o qual eu já estava acostumado. No artigo um shell
script é escrito para fazer o backup e a retenção, mas eu vi em um comentário
mencionando que existia uma ferramente que fazia exatamente isso, chamada
&lt;a class="reference external" href="https://rsnapshot.org/"&gt;rsnapshot&lt;/a&gt;. Então eu decidi usar rsync + rsnapshot como minha solução de backup.&lt;/p&gt;
&lt;p&gt;Em pouco tempo eu já comecei a me deparar com problemas. O maior deles era que o
rsnapshot só consegue fazer backups para um armazenamento local. Isso significa
que eu precisava rodar ele na raspberry, que estava conectada no disco rígido
onde os backups iriam ficar, e de lá fazer backup dos arquivos do meu
computador.&lt;/p&gt;
&lt;p&gt;Já que a raspberry precisava conectar no meu notebook, o normal a se fazer seria
configurar um IP fixo nele, mas já que eu carregava ele comigo para outras
redes, eu pensei que seria melhor mantê-lo com IP dinâmico. Então eu acabei com
uma solução não-exatamente-elegante em que o meu notebook rodava o cronjob para
o backup, no qual ele conectava à raspberry, atualizava seu IP nela, e rodava o
rsnapshot nela. O rsnapshot então usava o IP atualizado para conectar de volta
ao notebook e fazer o backup.&lt;/p&gt;
&lt;p&gt;Para tornar os dados seguros, o disco era criptografado usando dm-crypt.
Portanto antes do rsnapshot começar o backup, o disco era desbloqueado usando a
senha, que ficava salva como um arquivo no sistema da raspberry (eu sei, muito
inseguro!). Depois do backup ele era bloqueado de novo e desligado.&lt;/p&gt;
&lt;p&gt;Outro problema era que o rsync não lida muito bem com renomeação ou
mudança de arquivos/diretórios. Ele pensa que o anterior foi deletado e um novo
foi criado. Isso era um grande problema já que eu estava sempre tentando achar a
melhor hierarquia para os meus arquivos, o que significava renomear ou mover os
diretórios base e de repente todos os meus arquivos precisavam ser copiados de
novo...&lt;/p&gt;
&lt;p&gt;Esse problema era agravado ainda mais pelo fato de que a transferência entre o
meu computador e a raspberry era feita sem fio, então era bem lenta. Para
contornar esse problema eu adicionei um pouco de lógica nos scripts de backup
para que se o tamanho total da transferência fosse muito grande, ela
simplesmente falhava e me notificava. Então eu olhava no diff, lembrava que eu
tinha renomeado alguns diretórios e copiava eles para o backup manualmente
através de um cabo Ethernet temporário.&lt;/p&gt;
&lt;p&gt;Por fim, como a fonte era bem barulhenta e eu também não queria deixá-la ligada
à toa, eu colocava um alarme diário no meu celular para me lembrar de apertar o
botãozinho para ligá-la logo antes da hora do backup diário. E depois eu
desligava antes de dormir.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="atualmente"&gt;
&lt;h2&gt;Atualmente&lt;/h2&gt;
&lt;p&gt;Um bom tempo passou desde então, e já que aquele sistema funcionava mas estava
longe de ser bom, eu pesquisei melhor sobre as ferramentas de backup que existem
para ver se achava algo melhor. Tudo mudou quando eu descobri o &lt;a class="reference external" href="https://www.borgbackup.org/"&gt;borg&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="https://www.borgbackup.org/"&gt;Borg&lt;/a&gt;, ou BorgBackup, é um programa para fazer backups. Ele consegue fazer
backups locais ou remotos. Os backups podem (e devem!) ser encriptados. E
deduplicados! Eu não tenho certeza se renomear/mover arquivos fazem o backup
levar mais tempo, mas no mínimo ele não vai ocupar mais espaço já que todos os
arquivos são divididos em pequenos pedaços e deduplicados independentemente do
caminho. Ah, e ele tem uma ótima documentação e uma comunidade ativa!&lt;/p&gt;
&lt;p&gt;Além disso, existe um outro projeto chamado &lt;a class="reference external" href="https://torsion.org/borgmatic/"&gt;borgmatic&lt;/a&gt;. O borgmatic permite que
toda a configuração do uso do borg seja feita em um único arquivo, como os
repositórios para usar, a política de retenção e os arquivos para ignorar. Então
basicamente enquanto o borg fornece os comandos para fazer os backups, o
borgmatic permite que tudo seja configurado em um único arquivo e ele cuida do
resto.&lt;/p&gt;
&lt;p&gt;No fim, borg + borgmatic fazem ser incrivelmente fácil de ter backups seguros e
confiáveis. Eu agora tenho uma única entrada simples no cron para fazer os
backups diários: &lt;code class="docutils literal"&gt;0 21 * * * cd ~/ &amp;amp;&amp;amp; borgmatic --no-color --verbosity 1
--files &amp;gt;&amp;gt; ~/ark/etc/bkplogs&lt;/code&gt;. É importante notar que o borg sempre usa
caminhos relativos nos backups, então por isso que eu dou &lt;code class="docutils literal"&gt;cd&lt;/code&gt; para a pasta
onde os arquivos que eu quero salvar estão (meu diretório home).&lt;/p&gt;
&lt;p&gt;Além disso, como toda a minha configuração dos backups é feita em um único
arquivo de configuração com o borgmatic, é fácil de fazer backup desse arquivo
também. Então se algum dia eu perder meus arquivos e precisar recuperar do
backup, eu vou imediatamente já ter a configuração do backup feita e pronto para
continuar fazendo backups.&lt;/p&gt;
&lt;p&gt;Mas é, o borg é realmente ótimo e muito melhor do que todas as outras soluções
que tentei antes. Tem um único detalhe, se você está fazendo backup para um
repositório remoto, o borg também tem que rodar lá. Então nesses casos você vai
ter um borg rodando na máquina local, fazendo a divisão dos arquivos em pedaços
e criptografando (para que o remoto já receba as partes criptografadas e seja
seguro!), e na máquina remota outra instância do borg vai estar recebendo eles e
armazenando-os no repositório.&lt;/p&gt;
&lt;p&gt;Dada essa restrição, você não pode fazer backup para um armazenamento remoto
simples na nuvem. Mas existem alternativas muito boas focadas em hospedar
backups de borg, como o &lt;a class="reference external" href="https://www.borgbase.com/"&gt;BorgBase&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Mas bem, eu ainda uso minha montagem da raspberry, só que agora eu instalei o
borg lá ao invés do rsnapshot, e removi todos os meus scripts estranhos. Ele é
agora apenas mais uma entrada no meu config do borgmatic. Fora isso, ao invés de
ter a senha do disco simplesmente jogada no sistema da raspberry eu tive a ideia
de enviá-la como parte do comando ssh da minha máquina, assim eu posso armazenar
a senha de forma segura na minha própria máquina que é criptografada. Ah, e
agora eu acabo fazendo os backups diários para um servidor remoto e apenas faço
esses backups locais uma vez por semana, assim eu não tenho que ter o trabalho
de apertar esse botãozinho todo dia 😉.&lt;/p&gt;
&lt;p&gt;Vale a pena mencionar que a recuperação de backups também é muito fácil com o
borg. Existem dois comandos (oferecidos tanto pelo borg quanto pelo borgmatic):
&lt;code class="docutils literal"&gt;extract&lt;/code&gt; e &lt;code class="docutils literal"&gt;mount&lt;/code&gt;. &lt;code class="docutils literal"&gt;extract&lt;/code&gt; recupera o(s) arquivo(s) de um dado
&lt;em&gt;archive&lt;/em&gt;. &lt;strong&gt;Note que ele vai ser salvo no mesmo caminho usado no archive,
possivelmente sobrescrevendo seus arquivos locais&lt;/strong&gt;, então melhor rodar ele
dentro de uma pasta temporária primeiro. &lt;code class="docutils literal"&gt;mount&lt;/code&gt; monta o &lt;em&gt;archive&lt;/em&gt; em uma
pasta local como se fosse um sistema de arquivos, permitindo que você navegue
pelos arquivos.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusao"&gt;
&lt;h2&gt;Conclusão&lt;/h2&gt;
&lt;p&gt;Como você pode ver, eu melhorei significativamente meu sistema de backup. E isso
porque ele é menos interessante, não o contrário. Apesar de eu ter boas
lembranças de ter meu próprio sistema, que dependia de múltiplos script para
resolver os problemas com o que eu tinha, era um saco de manter. É ótimo ter
tudo funcionando de cara com o borg e o borgmatic: Eu consigo até esquecer que
eu estou fazendo backups agora, o que é um alívio.&lt;/p&gt;
&lt;/div&gt;
</content><category term="2021"/><category term="backup"/><category term="borg"/></entry><entry><title>Domando meu Kindle</title><link href="https://nfraprado.net/pt-br/post/domando-meu-kindle.html" rel="alternate"/><published>2021-08-29T00:00:00-03:00</published><updated>2021-08-29T00:00:00-03:00</updated><author><name>Nícolas F. R. A. Prado</name></author><id>tag:nfraprado.net,2021-08-29:/pt-br/post/domando-meu-kindle.html</id><summary type="html">&lt;p&gt;Vários anos atrás minha mãe me deu um Kindle Paperwhite 2. Eu li alguns
livros nele desde então, mas eu nunca senti que ele realmente era meu. Bloquear
a tela mostrava um anúncio, todos os livros comprados da Amazon eram protegidos
por DRM, livros transferidos via USB só funcionavam se …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Vários anos atrás minha mãe me deu um Kindle Paperwhite 2. Eu li alguns
livros nele desde então, mas eu nunca senti que ele realmente era meu. Bloquear
a tela mostrava um anúncio, todos os livros comprados da Amazon eram protegidos
por DRM, livros transferidos via USB só funcionavam se estivessem no formato
.mobi da Amazon, e eu sempre me sentia desconfortável sabendo que cada página
que eu virasse podia estar sendo reportada de volta para a central deles.&lt;/p&gt;
&lt;p&gt;Recentemente eu decidi dar um basta nisso e descobrir como fazer jailbreak no
meu Kindle. Eu estava disposto a torná-lo meu ou perdê-lo no processo.&lt;/p&gt;
&lt;div class="section" id="fazendo-o-jailbreak"&gt;
&lt;h2&gt;Fazendo o Jailbreak&lt;/h2&gt;
&lt;p&gt;Depois de pesquisar um pouco achei &lt;a class="reference external" href="https://www.mobileread.com/forums/showthread.php?t=320564"&gt;um artigo muito bem mantido em um fórum
sobre como fazer jailbreak no Kindle&lt;/a&gt;. Na verdade esse fórum
mobileread.com inteiro parece ser o ponto focal de informações sobre fazer
jailbreak e customizações em dispositivos de leitura de e-book. Eu acho incrível
como existem essas bolhas na internet cheias de informações específicas e que
você nunca encontra a não ser que procure por elas.&lt;/p&gt;
&lt;p&gt;Eu comecei a seguir o guia, que é na verdade bem simples: o jailbreak em si
consiste em conectar o Kindle no computador pelo USB, adicionar um arquivo nele
e fazer o arquivo rodar. Mas nesse momento eu percebi um grande problema que eu
já suspeitava: o USB no meu Kindle não funcionava mais.&lt;/p&gt;
&lt;p&gt;A porta micro USB no Kindle serve tanto para transferência de energia, para
carregar o dispositivo, quanto transferência de dados, para ser possível montar
ele no computador como um dispositivo de armazenamento de dados e transferir
arquivos. Mas de alguma forma a transmissão de dados não funcionava mais, então
eu não conseguia enviar arquivos para o Kindle. Pelo menos a energia ainda
funcionava, então eu ainda conseguia recarregar ele.&lt;/p&gt;
&lt;p&gt;Com o principal canal de dados para o Kindle quebrado, eu percebi que minhas
alternativas eram ou descobrir o problema no USB, que eu imaginava que
seria problema no hardware do controlador USB e precisaria trocar o CI por um
novo, ou checar se o Kindle tinha uma interface UART funcional que eu poderia
usar no lugar do USB. Essa segunda opção parecia bem mais fácil se fosse
possível.&lt;/p&gt;
&lt;div class="section" id="fazendo-o-jailbreak-pela-uart"&gt;
&lt;h3&gt;Fazendo o Jailbreak pela UART&lt;/h3&gt;
&lt;p&gt;De fato era possível acessar a UART do Kindle PW2 como &lt;a class="reference external" href="https://www.mobileread.com/forums/showthread.php?t=267541"&gt;esse artigo&lt;/a&gt; mostra. Ele também mostra como abrir o Kindle
e dá algumas ideias de como tornar o acesso ao UART permanente.&lt;/p&gt;
&lt;p&gt;A única parte difícil em abrir foi o primeiro passo: fazer a tampa da frente
desgrudar do resto. Tendo parado de tocar violão alguns anos atrás, eu nunca
pensei que esse seria o jeito que uma palheta seria útil para mim de novo 😝:&lt;/p&gt;
&lt;img alt="{image}/open-pick.jpg" src="/images/kindle-jailbreak/open-pick.jpg" /&gt;
&lt;p&gt;Com a ajuda da palheta eu desgrudei a tampa e depois de desparafusar alguns
parafusos eu cheguei nas entranhas (a segunda foto mostra os parafusos da PCB
também desparafusados e cabos flat desconectados já que eu estava fuçando, mas
não tem nada de interessante debaixo da PCB):&lt;/p&gt;
&lt;img alt="{image}/open-nocover.jpg" src="/images/kindle-jailbreak/open-nocover.jpg" /&gt;
&lt;img alt="{image}/open-board.jpg" src="/images/kindle-jailbreak/open-board.jpg" /&gt;
&lt;p&gt;Nessa etapa eu já estava pronto para testar se a UART realmente funcionava. Um
problema é que a UART do Kindle usa 1.8V e meu cabo UART é 3.3V. Para contornar
esse problema eu usei um divisor de tensão para abaixar a tensão para o RX do
Kindle, e dois transistores bipolares e um regulador de tensão de 3.3V para
aumentar a tensão do TX do Kindle (tenho que admitir que eu pesquisei na
internet por esse circuito, mas pelo menos depois que eu vi eu fui capaz de
verificar como funcionava e vou lembrar dele para projetos futuros 🙂). Essa foi
a configuração temporária:&lt;/p&gt;
&lt;img alt="{image}/temp-mod-uart.jpg" src="/images/kindle-jailbreak/temp-mod-uart.jpg" /&gt;
&lt;p&gt;Com isso eu rodei o &lt;code class="docutils literal"&gt;picocom&lt;/code&gt; na porta serial e reiniciei o Kindle. Essa foi a
saída que eu obtive (só pedaços para não ficar muito longo):&lt;/p&gt;
&lt;pre class="code literal-block"&gt;
Terminal ready
Battery voltage: 4072 mV

Hit any key to stop autoboot:  0 
## Booting kernel from Legacy Image at 80800000 ...
   Image Name:   Linux-3.0.35-lab126
   Image Type:   ARM Linux Kernel Image (uncompressed)
   Data Size:    3054728 Bytes =  2.9 MB
   Load Address: 80008000
   Entry Point:  80008000
   Verifying Checksum ... OK
   Loading Kernel Image ... OK
OK

Starting kernel ...

[    0.097278] boot: C def:bcut:batterycut=1
[    0.274436] LPDDR2 MfgId: 0x1 [Samsung]

[...]

3.0.35-lab126 #2 PREEMPT Wed Sep 25 00:44:40 UTC 2019 armv7l
Press [ENTER] for recovery menu...       0 /BOOTING DEFAULT.
IP-Config: no devices to configure
kinit: Mounted root (ext3 filesystem) readonly.

[...]

info milestone:7.91:sy99:sy99
info system:done:time=7940:time=7940
crond[849]: crond (busybox 1.28.3) started, log level 8

init.exe: sshd main process (848) terminated with status 127
init.exe: sshd ain process endeinit.exe: recevnt pre-start proc


Welcome to Kindle!

kindle login: info milestone:8.24:sc01:sc01
info system_fs_loopbacks:mountingfs:Mounting compressed directories filesystem images:Mounting compressed directories filesystem images
info milestone:8.27:sc02:sc02
info milestone:8.31:/usr/share/X11/xkb:/usr/share/X11/xkb

[...]

info clickstreamHeartbeatMetricsFramework:Skipping metrics recording, as it is the same day::
info monitor:writing_file:file=/var/run/upstart/stored.restarts:file=/var/run/upstart/stored.restarts
Retrieved 383 keys for system/daemon/pmond/
&lt;/pre&gt;
&lt;p&gt;Opa, oi Linux 3.0, há quanto tempo você está aí? 🙂&lt;/p&gt;
&lt;p&gt;Enviar comandos pelo serial também estava funcionando, então eu estava pronto
para seguir com o jailbreak. Eu continuei seguindo o &lt;a class="reference external" href="https://www.mobileread.com/forums/showthread.php?t=267541"&gt;artigo sobre o jailbreak
pelo serial&lt;/a&gt; até que consegui root, mas para minha surpresa depois disso ele
mostra como transferir o arquivo do jailbreak pelo armazenamento USB, que eu não
posso usar. Mas já que consegui fazer o serial funcionar, eu não via porque não
poderia usá-lo para transferir o arquivo, apesar de nunca ter feito isso antes.&lt;/p&gt;
&lt;p&gt;Mas claro que isso é possível, e descobri que é feito usando os protocolos de
transferência de arquivos xmodem, ymodem ou modem. Todos eles são providos pelo
pacote &lt;a class="reference external" href="https://archlinux.org/packages/community/x86_64/lrzsz/"&gt;lrzsz&lt;/a&gt; no Arch Linux. Já que o Kindle apenas possuía o binário do &lt;code class="docutils literal"&gt;rx&lt;/code&gt;,
que é o receptor do xmodem, eu precisava usar o &lt;code class="docutils literal"&gt;sx&lt;/code&gt; no meu computador, para
enviar usando o protocolo xmodem também.&lt;/p&gt;
&lt;p&gt;O procedimento para enviar um arquivo pelo serial usando o xmodem é:&lt;/p&gt;
&lt;ul class="simple" id="procedimento-do-xmodem"&gt;
&lt;li&gt;rodar &lt;code class="docutils literal"&gt;picocom&lt;/code&gt; passando &lt;code class="docutils literal"&gt;--send-cmd 'sx'&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;no serial (o shell do Kindle no meu caso), digitar &lt;code class="docutils literal"&gt;rx &amp;lt;filename&amp;gt;&lt;/code&gt; onde
&lt;code class="docutils literal"&gt;filename&lt;/code&gt; é o nome que vai ser dado para o arquivo recebido no sistema de
arquivo destino.&lt;/li&gt;
&lt;li&gt;apertar &lt;code class="docutils literal"&gt;Ctrl+a&lt;/code&gt; seguido de &lt;code class="docutils literal"&gt;Ctrl+s&lt;/code&gt;. Isso vai instruir o picocom a enviar
o arquivo, usando o &lt;code class="docutils literal"&gt;sx&lt;/code&gt; para a transferência já que foi ele que foi passado
como o &lt;code class="docutils literal"&gt;--send-cmd&lt;/code&gt; na hora de rodar o picocom.&lt;/li&gt;
&lt;li&gt;no prompt que aparece, digitar o nome do arquivo que vai ser transferido da
máquina origem, relativo ao diretório que o picocom foi executado.&lt;/li&gt;
&lt;li&gt;esperar a transferência terminar (pode levar um tempo).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Vale ressaltar um detalhe importante em transferir arquivos dessa forma e que eu
levei um tempo para descobrir, e que talvez seja específico da minha conexão com
o Kindle, não tenho certeza, mas é que &lt;strong&gt;a transferência só vai funcionar se o
Kindle estiver carregando&lt;/strong&gt;. Por algum motivo, parece que quando ele não está
carregando os sinais TX/RX não são estáveis o suficiente a ponto dos &lt;em&gt;checksum&lt;/em&gt;
que são enviados durante a transferência do xmodem não serem válidos e a
transferência falha. Mas com o carregador conectado e seguindo os passos
descritos acima, eu não tive nenhum problema em transferir arquivos. Usar o
shell, por outro lado, funciona suficientemente bem mesmo sem ter o carregador
conectado.&lt;/p&gt;
&lt;p&gt;A única coisa é que a transferência é demorada. A velocidade de transferência é
aproximadamente 10 KB/s, então o arquivo de jailbreak (~160 KB) levou 15
segundos para transferir, já arquivos grandes como o pacote do KOReader (~37
MB), que vou mencionar logo abaixo, levou por volta de uma hora. Pelo USB o
primeiro teria sido instantâneo, e o segundo teria levado alguns segundos. Isso
me fez perceber o quanto evoluímos em velocidade de transferência, então esse
método tem seu próprio charme 😝.&lt;/p&gt;
&lt;p&gt;Com essa configuração eu pude transferir o arquivo, seguir o restante do guia e
completar o jailbreak do meu Kindle com sucesso! Eu também instalei o KUAL e o
MRInstaller como sugerido nos guias para que pacotes sejam fáceis de instalar e
possam ser acessados por uma GUI no Kindle.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="tornando-ele-meu"&gt;
&lt;h2&gt;Tornando ele meu&lt;/h2&gt;
&lt;p&gt;Agora que eu tinha um Kindle com jailbreak era hora de finalmente torná-lo meu.
A necessidade de usar o formato mobi é irritante, eu preferiria usar um formato
mais padrão como o epub. Felizmente isso é possível em um Kindle com jailbreak
usando a aplicação &lt;a class="reference external" href="https://github.com/koreader/koreader"&gt;KOReader&lt;/a&gt;.&lt;/p&gt;
&lt;div class="section" id="koreader"&gt;
&lt;h3&gt;KOReader&lt;/h3&gt;
&lt;p&gt;O &lt;a class="reference external" href="https://github.com/koreader/koreader"&gt;KOReader&lt;/a&gt; é um leitor de e-book que roda em Kindles com jailbreak (e também
outros dispositivos) e suporta múltiplos formatos de e-book como epub e pdf, que
é exatamente o que eu queria.&lt;/p&gt;
&lt;p&gt;É sinceramente um ótimo programa: Ele é muito ativamente mantido e melhorado, e
tem todas as funcionalidades que eu estava acostumado no leitor padrão do Kindle
como marcar páginas, pesquisar no dicionário, pesquisar no texto e navegar pelos
capítulos, além de muitas outras e diversas configurações dentro dos seus vários
menus.&lt;/p&gt;
&lt;p&gt;Minhas funcionalidades preferidas, que são melhorias em relação ao leitor padrão
do Kindle, são a barra de progresso na parte inferior da tela mostrando sua
localização atual no livro, e o fato de que quando o dispositivo é bloqueado, a
capa do livro é mostrada na tela! 😃 Isso me deixou muito feliz de ver. Sem mais
anúncios ou imagens aleatórias da Amazon, só a capa do livro que eu estou lendo.
Na verdade, eu originalmente tinha instalado o ScreenSavers Hack para ser
possível colocar um bloqueio de tela logo depois que fiz o jailbreak no Kindle,
mas depois que percebi que o KOReader já tinha essa funcionalidade embutida eu
removi esse pacote.&lt;/p&gt;
&lt;p&gt;Minha única queixa com o KOReader é que às vezes ele fica bem lento, e então eu
preciso reiniciá-lo (que leva só alguns segundos) e ele volta à velocidade
normal. Mas eu provavelmente deveria atualizá-lo já que eles liberam uma nova
versão todo mês e já fazem quatro meses desde que instalei, então esse problema
pode já ter sido resolvido. (Sim, tudo descrito nesse artigo aconteceu por volta
de abril)&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="conector-uart"&gt;
&lt;h3&gt;Conector UART&lt;/h3&gt;
&lt;p&gt;A fim de tornar meu Kindle à prova de vigilância eu precisava manter o WiFi
sempre desligado (também para manter o jailbreak funcionando). E já que
transferir via USB não funciona mais, o UART seria minha única interface com o
Kindle. Isso significa que eu preciso conseguir transferir os livros para o
Kindle através do UART com certa facilidade. Eu não considero precisar abri-lo
fácil o suficiente então eu precisava de um acesso ao UART mais definitivo.&lt;/p&gt;
&lt;p&gt;A ideia era basicamente fazer os fios conectados aos pontos de UART na PCB
ficarem mais fixos, abrir um buraco na lateral da tampa de plástico do Kindle e
expor alguns pinos ali para que fosse fácil conectar um cabo UART no Kindle sem
precisar abri-lo.&lt;/p&gt;
&lt;p&gt;Um problema que já mencionei é que a tensão de operação do meu cabo UART
é diferente da do Kindle, o que foi o motivo para eu ter feito o circuito
conversor na protoboard. Eu inicialmente pensei em colocar o circuito conversor
dentro do próprio Kindle, mas percebi que eu não tinha componentes pequenos o
suficiente para que coubesse ali. Então no fim acabei aceitando que seria
necessário um circuito conversor externo.&lt;/p&gt;
&lt;p&gt;A abertura na tampa para expor os pinos de serial foi feita pelo meu pai! 😃&lt;/p&gt;
&lt;img alt="{image}/mod-case.jpg" src="/images/kindle-jailbreak/mod-case.jpg" /&gt;
&lt;img alt="{image}/mod-case2.jpg" src="/images/kindle-jailbreak/mod-case2.jpg" /&gt;
&lt;img alt="{image}/mod-case3.jpg" src="/images/kindle-jailbreak/mod-case3.jpg" /&gt;
&lt;p&gt;A solda dos fios nos pontos de UART da PCB foi um pouco complicada já que os
pontos são bem pequenos. Mas com um pouco de ajuda de fita isolante para
segurar o fio enquanto eu soldava e escolhendo um fio adequado (aqueles usados
dentro de cabos Ethernet funcionaram bem) eu dei um jeito.&lt;/p&gt;
&lt;p&gt;Ainda sim eu estava preocupado em esbarrar no fio sem querer e desconectar ele,
ou pior ainda, causar algum dano irreparável no ponto de UART, então eu acabei
grudando os fios com fita isolante na placa a cada alguns centímetros, como
&lt;em&gt;checkpoints&lt;/em&gt;, e funcionou super bem. Eu vou certamente continuar usando essa
técnica no futuro.&lt;/p&gt;
&lt;img alt="{image}/mod-uart-connection.jpg" src="/images/kindle-jailbreak/mod-uart-connection.jpg" /&gt;
&lt;p&gt;Eu deixei um pouco de fio sobrando caso seja necessário no futuro.&lt;/p&gt;
&lt;img alt="{image}/mod-uart-wires.jpg" src="/images/kindle-jailbreak/mod-uart-wires.jpg" /&gt;
&lt;p&gt;Fazer o fio ficar bem conectado nos pinos do conector foi complicado também. Eu
torci cada fio em volta de cada pino, mas não tão apertado para que o fio não
quebrasse (isso aconteceu algumas vezes), e adicionei um pouco de solda. Depois
disso eu coloquei fita isolante em volta de cada pino para que os fios de cada
pino não tocassem um ao outro. Por fim adicionei mais fita isolante em volta de
todos os pinos para isolar eles do corpo cinzento do Kindle, já que ele é um
material condutor.&lt;/p&gt;
&lt;img alt="{image}/mod-uart-header.jpg" src="/images/kindle-jailbreak/mod-uart-header.jpg" /&gt;
&lt;p&gt;Depois de centralizar razoavelmente os pinos no buraco, eu coloquei a &lt;a class="reference external" href="https://www.loctite-consumo.com.br/pt/produtos/durepoxi.html"&gt;durepoxi&lt;/a&gt;
e moldei ela em volta dos pinos, em cima e embaixo, para que ficassem bem fixos
no corpo do Kindle.&lt;/p&gt;
&lt;img alt="{image}/mod-uart-epoxy.jpg" src="/images/kindle-jailbreak/mod-uart-epoxy.jpg" /&gt;
&lt;p&gt;Depois de deixar secar durante a noite, a cirurgia estava completa:&lt;/p&gt;
&lt;img alt="{image}/mod-final.jpg" src="/images/kindle-jailbreak/mod-final.jpg" /&gt;
&lt;p&gt;Ah, e eu fiz tudo isso duas vezes. A primeira vez eu cheguei até o fim e só
então percebi que dois dos pinos estavam curto circuitados pelo corpo cinzento
do Kindle. Felizmente a durepoxi não estava totalmente rígida ainda então eu
consegui removê-la com um pouco de perseverança e tentar de novo.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="resultado"&gt;
&lt;h2&gt;Resultado&lt;/h2&gt;
&lt;p&gt;Então agora eu tenho um Kindle com os pinos UART expostos, e foi assim que
ficou:&lt;/p&gt;
&lt;img alt="{image}/result-pin.jpg" src="/images/kindle-jailbreak/result-pin.jpg" /&gt;
&lt;img alt="{image}/result-front.jpg" src="/images/kindle-jailbreak/result-front.jpg" /&gt;
&lt;p&gt;Agora quando eu quero ler um novo livro, eu conecto o carregador e a placa
conversora nele (eu ainda estou usando a protoboard para o circuito conversor
porque ainda não superei a preguiça para fazer uma placa decente, mas não seria
difícil e seria bem mais compacta):&lt;/p&gt;
&lt;img alt="{image}/result-transfer-setup.jpg" src="/images/kindle-jailbreak/result-transfer-setup.jpg" /&gt;
&lt;p&gt;Aí eu sigo o &lt;a class="reference internal" href="#procedimento-do-xmodem"&gt;procedimento do xmodem&lt;/a&gt; que mencionei acima para enviar o arquivo
do livro pelo UART e espero um pouco até a transferência acabar...&lt;/p&gt;
&lt;pre class="code literal-block"&gt;
[root&amp;#64;kindle documents]# rx sherlock_holmes.epub
C
*** file: ext/books/sherlock_holmes.epub
$ sx ext/books/sherlock_holmes.epub
Sending ext/books/sherlock_holmes.epub, 16078 blocks: Give your local XMODEM receive command now.
Bytes Sent:2058112   BPS:10396                           

Transfer complete

*** exit status: 0 ***
[root&amp;#64;kindle documents]#
&lt;/pre&gt;
&lt;p&gt;Esse livro levou 3 minutos para transferir. O tamanho padrão de livros epub é
entre 100 KB e 10 MB, então a transferência leva entre 10 segundos e 20 minutos
dependendo do livro. É significantemente mais tempo do que levaria por USB
(alguns segundos) mas já que eu levo entre um e dois meses para acabar de ler
cada livro acaba não sendo um problema.&lt;/p&gt;
&lt;p&gt;Quando a transferência termina, tudo que resta é desconectar os cabos e
aproveitar a leitura! 🙂&lt;/p&gt;
&lt;img alt="{image}/result-book.jpg" src="/images/kindle-jailbreak/result-book.jpg" /&gt;
&lt;p&gt;(Eu uso essa capa azul no Kindle para protegê-lo, e os pinos são curtos o
suficiente que eles cabem embaixo também)&lt;/p&gt;
&lt;p&gt;Obs: Esse livro está disponível em domínio público e pode ser &lt;a class="reference external" href="https://www.gutenberg.org/ebooks/48320"&gt;baixado de graça
no Project Gutenberg&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusao"&gt;
&lt;h2&gt;Conclusão&lt;/h2&gt;
&lt;p&gt;Deu um pouco mais de trabalho do que eu esperava inicialmente, mas depois disso
tudo eu estou feliz que finalmente realmente sou o dono do meu Kindle e posso
ter uma experiência de leitura melhor nele 🙂.&lt;/p&gt;
&lt;/div&gt;
</content><category term="2021"/><category term="kindle"/></entry><entry><title>Customizações do blog</title><link href="https://nfraprado.net/pt-br/post/customizacoes-do-blog.html" rel="alternate"/><published>2021-07-24T00:00:00-03:00</published><updated>2021-07-24T00:00:00-03:00</updated><author><name>Nícolas F. R. A. Prado</name></author><id>tag:nfraprado.net,2021-07-24:/pt-br/post/customizacoes-do-blog.html</id><summary type="html">&lt;p&gt;Eu tenho usado o &lt;a class="reference external" href="https://github.com/getpelican/pelican/"&gt;pelican&lt;/a&gt; como meu gerador de blog estático desde que comecei
esse blog um pouco mais de um ano atrás. Só precisou de um pouco de configuração
para ter o blog funcionando, e um pouco de pesquisa no &lt;a class="reference external" href="http://pelicanthemes.com/"&gt;pelicanthemes&lt;/a&gt; para
achar o &lt;a class="reference external" href="https://github.com/gunchu/nikhil-theme"&gt;nikhil-theme&lt;/a&gt; que é o tema …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Eu tenho usado o &lt;a class="reference external" href="https://github.com/getpelican/pelican/"&gt;pelican&lt;/a&gt; como meu gerador de blog estático desde que comecei
esse blog um pouco mais de um ano atrás. Só precisou de um pouco de configuração
para ter o blog funcionando, e um pouco de pesquisa no &lt;a class="reference external" href="http://pelicanthemes.com/"&gt;pelicanthemes&lt;/a&gt; para
achar o &lt;a class="reference external" href="https://github.com/gunchu/nikhil-theme"&gt;nikhil-theme&lt;/a&gt; que é o tema que eu ainda uso.&lt;/p&gt;
&lt;p&gt;Apesar da configuração base do blog ter sido fácil, ela não ficou perfeitamente
adequada para o meu uso. Foi aí que eu comecei a customizar algumas coisas para
deixar o blog sob medida para mim. Felizmente o pelican é &lt;a class="reference external" href="https://docs.getpelican.com/en/stable/index.html"&gt;muito
customizável&lt;/a&gt;, a ponto de oferecer uma interface para plugins além das
configurações normais. Ele também é escrito em python, então se tudo der errado,
sempre existe a alternativa de hackear um patch para fazer o serviço 😝.&lt;/p&gt;
&lt;p&gt;Então como prometido no artigo anterior, nesse eu vou falar sobre as principais
customizações que eu fiz no meu blog até o momento. Ao longo do texto eu vou
referenciar os commits em que eu fiz as mudanças mencionadas aqui.&lt;/p&gt;
&lt;div class="section" id="customizacoes"&gt;
&lt;h2&gt;Customizações&lt;/h2&gt;
&lt;div class="section" id="plugins-do-pelican"&gt;
&lt;h3&gt;Plugins do pelican&lt;/h3&gt;
&lt;p&gt;As customizações que tiveram mais impacto foram provavelmente os plugins de
pelican que eu adicionei. Eles não são minhas modificações, mas dada sua
importância eu quero mencioná-los mesmo assim.&lt;/p&gt;
&lt;p&gt;Os dois plugins que eu estou usando atualmente são &lt;a class="reference external" href="https://github.com/getpelican/pelican-plugins/tree/master/i18n_subsites"&gt;i18n_subsites&lt;/a&gt; e &lt;a class="reference external" href="https://github.com/pelican-plugins/series"&gt;series&lt;/a&gt;, e
foram bem fáceis de encontrar pelo &lt;a class="reference external" href="https://github.com/getpelican/pelican-plugins/"&gt;repositório de plugins&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;O i18n_subsites é ótimo para o meu blog com idioma duplo. Por padrão o pelican
suporta apenas traduzir artigos do blog como textos independentes, de forma que
cada artigo que tenha sido traduzido possui links para as traduções. Mas o que
eu realmente queria era ter duas versões do meu blog, uma em inglês e outra em
português. Cada uma com toda a interface na sua própria língua além de apenas
mostrar os artigos dessa língua. Esse plugin possibilita exatamente isso.&lt;/p&gt;
&lt;p&gt;É um pouco confuso de entender como configurar corretamente no começo,
principalmente a &lt;a class="reference external" href="https://github.com/getpelican/pelican-plugins/blob/master/i18n_subsites/localizing_using_jinja2.rst"&gt;parte de localização do template&lt;/a&gt;, mas bom, eu aprendi sobre
Jinja2 e gettext! Commits relevantes: &lt;a class="reference external" href="https://codeberg.org/nfraprado/blog/commit/f538d7ebf78f9b93dd046d6aa44eaae08b287b66"&gt;1&lt;/a&gt; (foi parte do commit inicial...), &lt;a class="reference external" href="https://codeberg.org/nfraprado/blog/commit/9a02ff6bd640ea3c79e9e66f72b68116bed17b38"&gt;2&lt;/a&gt;,
&lt;a class="reference external" href="https://codeberg.org/nfraprado/blog/commit/604ca272908e77d50bc459ba51f45e47fbfb30bd"&gt;3&lt;/a&gt;, &lt;a class="reference external" href="https://codeberg.org/nfraprado/blog/commit/c2effc722cf01b834c7f2373bd6ef8791e4adc7d"&gt;4&lt;/a&gt; e &lt;a class="reference external" href="https://codeberg.org/nfraprado/blog/commit/d23b1151f12db58a9f9b4bbdbd9389cf6fdf53e4"&gt;5&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;O series eu adicionei um pouco depois. Ele é muito simples mas útil. A propósito
você pode vê-lo em ação nesse mesmo artigo no topo. Ele é o que me permite fazer
esse artigo ser parte de uma série de artigos, com link para os outros. Commits
relevantes: &lt;a class="reference external" href="https://codeberg.org/nfraprado/blog/commit/b6f481df611fa1b2e0815e2eeaf2b0a7a8016f26"&gt;1&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="feed-rss-separado-para-cada-sub-site-i18n"&gt;
&lt;h3&gt;Feed RSS separado para cada sub-site i18n&lt;/h3&gt;
&lt;p&gt;Ainda no tema de tradução, já que eu queria sub-sites separados para cada
idioma, também fazia sentido ter um feed RSS para cada um deles. No fim isso
acabou sendo uma mudança de uma única linha, apesar de ter demorado um pouco
para eu descobrir como fazer isso. Commits relevantes: &lt;a class="reference external" href="https://codeberg.org/nfraprado/blog/commit/dee307122f54e04f9aaa5a203b29a9c029a5f88f"&gt;1&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="deteccao-automatica-do-idioma-pelo-nome-do-arquivo"&gt;
&lt;h3&gt;Detecção automática do idioma pelo nome do arquivo&lt;/h3&gt;
&lt;p&gt;Outra customização relacionada a tradução (foi algo bem importante pro meu blog!
😝) foi a configuração automática do idioma de um artigo com base no nome do
arquivo. Já que eu já quero que o arquivo para cada tradução de um artigo tenha
o idioma em seu nome, isso me poupa de ter que adicionar o cabeçalho de idioma
também.&lt;/p&gt;
&lt;p&gt;Por exemplo, os arquivos para este artigo são chamados
&lt;code class="docutils literal"&gt;07-blog-custom-en.rst&lt;/code&gt;, para a versão em inglês, e &lt;code class="docutils literal"&gt;07-blog-custom-br.rst&lt;/code&gt;
para a versão em português. Normalmente eu também precisaria configurar o
cabeçalho de metadados &lt;code class="docutils literal"&gt;lang&lt;/code&gt; para cada um com o idioma correspondente, mas
com essa mudança ele é configurado automaticamente 🙂. Commits relevantes: &lt;a class="reference external" href="https://codeberg.org/nfraprado/blog/commit/f538d7ebf78f9b93dd046d6aa44eaae08b287b66#4cd8c938f4dfb9b2579dcfaf97c623df6ca28eb8_0_23"&gt;1&lt;/a&gt;
(de novo, parte do commit inicial 😅) e &lt;a class="reference external" href="https://codeberg.org/nfraprado/blog/commit/1712f5b44b2d51de410348462c9d8a66d8b210fa"&gt;2&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="configuracao-de-icone-para-paginas"&gt;
&lt;h3&gt;Configuração de ícone para páginas&lt;/h3&gt;
&lt;p&gt;Quando eu estava configurando a barra de navegação no topo, eu queria adicionar
um ícone para cada item, para deixar bem claro o que cada um faz. Mas a página
Sobre, diferentemente dos outros, é uma página gerada do fonte (assim como os
artigos) e adicionada automaticamente à barra, então não é possível configurar
seu ícone direto no HTML.&lt;/p&gt;
&lt;p&gt;Para resolver isso eu criei um cabeçalho de metadado customizado &lt;code class="docutils literal"&gt;icon&lt;/code&gt; que
armazena o nome do ícone FontAwesome que será mostrado na barra para a página.
Aí eu apenas adicionei o nome daquele ícone de &amp;quot;círculo de informação&amp;quot; para a
página Sobre e a aparência ficou bem melhor 🙂. Commits relevantes: &lt;a class="reference external" href="https://codeberg.org/nfraprado/blog/commit/76534d397a83117327aa1381241b412c77a47b7b"&gt;1&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="conserto-da-renderizacao-de-literal-no-rst"&gt;
&lt;h3&gt;Conserto da renderização de literal no rst&lt;/h3&gt;
&lt;p&gt;Vamos finalizar o artigo com a maior mudança 😝.&lt;/p&gt;
&lt;p&gt;Primeiro de tudo, um pouco de contexto: Quando eu comecei o blog, eu escrevia
meus artigos em Markdown. Mas alguns meses depois eu conheci o &lt;a class="reference external" href="https://docutils.sourceforge.io/rst.html"&gt;reStructuredText&lt;/a&gt;
(daqui para frente, chamado de rst) porque ele era usado na documentação do
Kernel Linux, e comecei a usar mais ainda quando descobri o &lt;a class="reference external" href="https://rst2pdf.org/"&gt;rst2pdf&lt;/a&gt;, que eu não
vou entrar em detalhes já que ainda pretendo escrever sobre ele eventualmente. O
ponto é que eu passei a escrever os meus artigos em rst já que ele também é
suportado pelo pelican.&lt;/p&gt;
&lt;p&gt;Mas um problema muito irritante que eu enfrentei foi que o gerador HTML padrão
do rst traduz blocos literais para tags &lt;code class="docutils literal"&gt;&amp;lt;tt&amp;gt;&lt;/code&gt;. Essa tag foi descontinuada no
HTML5, e o mundo inteiro usa &lt;code class="docutils literal"&gt;&amp;lt;code&amp;gt;&lt;/code&gt; para código no meio do texto, incluindo
meu tema, o que significa que literais não eram renderizados corretamente.
(&lt;a class="reference external" href="https://sourceforge.net/p/docutils/mailman/docutils-develop/thread//p/docutils/bugs/393/de24b5cc7539766427b108223bbcedb8ea8aef5f.bugs%40docutils.p.sourceforge.net/#msg37057965"&gt;Existem motivos&lt;/a&gt; para o rst não usar &lt;code class="docutils literal"&gt;&amp;lt;code&amp;gt;&lt;/code&gt; para literais, mas para o
meu blog eles não são relevantes)&lt;/p&gt;
&lt;p&gt;Eu poderia ter simplesmente adaptado meu tema para contornar esse problema, mas
isso seria apenas escondê-lo: A tag descontinuada &lt;code class="docutils literal"&gt;&amp;lt;tt&amp;gt;&lt;/code&gt; continuaria ali para
todo o mundo ver.&lt;/p&gt;
&lt;p&gt;Em um primeiro momento, eu contornei esse problema usando o &lt;em&gt;role&lt;/em&gt; &lt;code class="docutils literal"&gt;code&lt;/code&gt;
explicitamente:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
O seguinte é um literal no rst, então ficará dentro de uma tag &amp;lt;tt&amp;gt;: ``bla``
O seguinte usa o role 'code', então ficará dentro de uma tag &amp;lt;code&amp;gt;: :code:`bla`
&lt;/pre&gt;
&lt;p&gt;Essa é claramente uma sintaxe muito extensa para um blog que frequentemente
usa código no meio do texto, então eu me cansei rapidamente dela. Eu comecei
então a redefinir o &lt;em&gt;role&lt;/em&gt; padrão para ser &lt;code class="docutils literal"&gt;code&lt;/code&gt;, e a usar um único símbolo
de crase ao invés dos dois que se usa para literais:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
.. default-role:: code

Agora o seguinte vai usar o role 'code': `bla`
&lt;/pre&gt;
&lt;p&gt;Já que o texto está entre um único símbolo de crase de cada lado mas não possui
um &lt;em&gt;role&lt;/em&gt; especificado, ele usará o padrão. Muito melhor, mas eu ainda
precisava escrever aquela definição de &lt;code class="docutils literal"&gt;default-role&lt;/code&gt; no começo de cada
documento...&lt;/p&gt;
&lt;p&gt;Finalmente, eu pensei em uma solução ainda melhor: Criar um plugin de leitor de
rst para o pelican que modifique o leitor padrão apenas quando estiver lidando
com literais, para que a saída seja &lt;code class="docutils literal"&gt;&amp;lt;code&amp;gt;&lt;/code&gt; ao invés de &lt;code class="docutils literal"&gt;&amp;lt;tt&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Honestamente isso foi mais fácil do que eu esperava. Só precisou copiar um pouco
dos leitores do pelican e do Sphinx e funcionou perfeitamente! Agora eu
finalmente conseguia escrever literais usando a sintaxe padrão do rst e ver eles
renderizados como &lt;code class="docutils literal"&gt;&amp;lt;code&amp;gt;&lt;/code&gt; no HTML sem nenhum trabalho a mais 🙂. Commits
relevantes: &lt;a class="reference external" href="https://codeberg.org/nfraprado/blog/commit/5349ef680c32f124cc425699749498f5debda0a1"&gt;1&lt;/a&gt; e &lt;a class="reference external" href="https://codeberg.org/nfraprado/blog/commit/bfb15a96a09ef7dd3403b9ed3e29f89a2a743731"&gt;2&lt;/a&gt; (esse é apenas eu atualizando todos os textos do blog para
usar a nova sintaxe).&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusao"&gt;
&lt;h2&gt;Conclusão&lt;/h2&gt;
&lt;p&gt;Com essas customizações, eu tenho um blog em que me sinto bastante confortável,
tanto para escrever quanto para ler. Essas mudanças que eu mostrei aqui foram as
que eu achei mais interessantes, mas claro que &lt;a class="reference external" href="https://codeberg.org/nfraprado/blog"&gt;todo o código é aberto&lt;/a&gt;,
então fique à vontade para ver o resto.&lt;/p&gt;
&lt;/div&gt;
</content><category term="2021"/><category term="blog"/></entry><entry><title>Um ano de blog</title><link href="https://nfraprado.net/pt-br/post/um-ano-de-blog.html" rel="alternate"/><published>2021-06-22T00:00:00-03:00</published><updated>2021-06-22T00:00:00-03:00</updated><author><name>Nícolas F. R. A. Prado</name></author><id>tag:nfraprado.net,2021-06-22:/pt-br/post/um-ano-de-blog.html</id><summary type="html">&lt;p&gt;Faz um ano que eu comecei esse blog! Pensei em aproveitar a oportunidade para
falar um pouco sobre o blog em si: Como começou e meus pensamentos sobre ele.&lt;/p&gt;
&lt;div class="section" id="origem"&gt;
&lt;h2&gt;Origem&lt;/h2&gt;
&lt;p&gt;Eu já tinha pensado em pouco sobre ter meu próprio blog. Ter um cantinho da
internet onde eu fosse livre …&lt;/p&gt;&lt;/div&gt;</summary><content type="html">&lt;p&gt;Faz um ano que eu comecei esse blog! Pensei em aproveitar a oportunidade para
falar um pouco sobre o blog em si: Como começou e meus pensamentos sobre ele.&lt;/p&gt;
&lt;div class="section" id="origem"&gt;
&lt;h2&gt;Origem&lt;/h2&gt;
&lt;p&gt;Eu já tinha pensado em pouco sobre ter meu próprio blog. Ter um cantinho da
internet onde eu fosse livre para falar sobre coisas que me interessam parecia
bem legal. E quem sabe outras pessoas também achariam elas interessantes!&lt;/p&gt;
&lt;p&gt;Mas eu não tenho interesse nenhum em desenvolvimento web, então eu só queria
escrever meus artigos sem precisar lidar com isso. Isso parecia apontar para
geradores de site prontos para uso como o WordPress, mas eu também não gostava
dessa ideia.&lt;/p&gt;
&lt;p&gt;Felizmente, já existia uma certa tendência a usar geradores de site estático.
Eles eram rápidos, simples de usar e você podia versionar o fonte no git e
hospedar o site de graça usando Gitlab/Github! Assim que eu soube da existência
deles eu sabia que era isso que eu queria.&lt;/p&gt;
&lt;p&gt;Na época dois dos meus amigos já tinham seus próprios blogs
(&lt;a class="reference external" href="https://rafaelg.net.br"&gt;https://rafaelg.net.br&lt;/a&gt; e &lt;a class="reference external" href="https://andrealmeid.com"&gt;https://andrealmeid.com&lt;/a&gt;) usando geradores de site
estático e eles me encorajaram a criar o meu também. Um deles me introduziu ao
&lt;a class="reference external" href="https://github.com/getpelican/pelican/"&gt;pelican&lt;/a&gt;, um gerador de blog estático em python e eu decidi experimentar ele.&lt;/p&gt;
&lt;p&gt;Depois de escolher um tema e um pouco de customização no pelican (que eu vou
falar sobre no próximo artigo), eu estava pronto para começar a escrever. E foi
então que eu comecei o blog com o &lt;a class="reference external" href="/pt-br/post/criando-listas-de-filmes-e-jogos-usando-taskwarrior.html"&gt;artigo "Criando listas de filmes e jogos usando Taskwarrior"&lt;/a&gt;, que é divertidamente
pessoal, e ainda sim técnico. Eu gosto desse estilo 🙂.&lt;/p&gt;
&lt;p&gt;Então foi assim que o blog começou. Agora eu queria ir um pouco mais a fundo nos
princípios que guiam o blog.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="principios"&gt;
&lt;h2&gt;Princípios&lt;/h2&gt;
&lt;p&gt;Existem duas coisas que eu acho que são bem importantes sobre o jeito que o blog
funciona: o idioma duplo e o formato de diário aberto.&lt;/p&gt;
&lt;div class="section" id="idioma-duplo"&gt;
&lt;h3&gt;Idioma duplo&lt;/h3&gt;
&lt;p&gt;Você deve ter notado que todos os artigos no meu blog têm um campo de
&amp;quot;traduções&amp;quot; com um outro idioma. E também há um botão para mudar o idioma na
barra de navegação. O motivo é que o meu blog está disponível tanto em inglês
quanto em português brasileiro. Para tornar isso possível eu traduzi a interface
para português e, mais importante, escrevo cada artigo tanto em inglês quanto em
português.&lt;/p&gt;
&lt;p&gt;A interface foi bem fácil de traduzir, mas precisar escrever cada artigo
praticamente duas vezes é bastante trabalho a mais. Então por que eu faço isso?&lt;/p&gt;
&lt;p&gt;Quando eu estava pensando em criar o blog, eu me deparei com um dilema: em qual
idioma deveria escrever?&lt;/p&gt;
&lt;p&gt;Por um lado, escrever em inglês faria meu conteúdo amplamente disponível, já que
é, na prática, a língua da internet. Seria possível por exemplo compartilhar um
artigo com uma comunidade com a qual eu contribuí.&lt;/p&gt;
&lt;p&gt;Por outro lado, eu gosto da minha língua. E eu queria poder compartilhar meus
artigos com pessoas que eu conheço pessoalmente sem um barreira invisível:
quando tanto eu quanto a pessoa somos mais familiares com português, parece sem
sentido transmitir a informação em inglês. Eu também não queria alienar pessoas,
do meu próprio país, que não falam inglês (apesar disso ser cada vez mais raro).&lt;/p&gt;
&lt;p&gt;Então eu decidi escolher os dois 🙂. O dobro do trabalho, mas nenhuma das
desvantagens e o dobro das vantagens. Na verdade não é o dobro do trabalho, já
que o segundo artigo é basicamente a tradução do outro e não um artigo do zero.
O fato de eu não publicar tão frequentemente (uma vez por mês) também ajuda.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="formato-de-diario-aberto"&gt;
&lt;h3&gt;Formato de diário aberto&lt;/h3&gt;
&lt;p&gt;Outra coisa é a forma que eu lido com meus artigos, e minha motivação. Ou a
resposta à pergunta: Já existe um zilhão de blogs por aí, por que alguém iria
querer ler o meu? Realmente vale a pena ter um blog?&lt;/p&gt;
&lt;p&gt;E minha resposta para isso é: Sim, vale, porque eu não estou escrevendo para os
outros.&lt;/p&gt;
&lt;p&gt;A principal razão de eu escrever no meu blog é porque eu quero registrar os
projetos que eu fiz e as coisas que eu achei interessantes, para que no futuro
eu possa olhar para trás e relembrar deles. Então é basicamente um diário
(só que mensal).&lt;/p&gt;
&lt;p&gt;Mas claro que se qualquer pessoa estiver interessada em qualquer um dos
assuntos, ela é livre para ler, o que torna o blog mais como um diário aberto.
Bem legal se acontecer, mas eu não conto com isso. Eu escrevo para mim mesmo,
mas se outras pessoas gostarem é um ótimo bônus 🙂.&lt;/p&gt;
&lt;p&gt;Esse é o motivo de eu não costumar fazer tutoriais. Ao invés disso documento
minha experiência no aprendizado ou construção de algo. Tem um quê bem mais
pessoal, apesar de não ser tão acessível.&lt;/p&gt;
&lt;p&gt;(Agora que eu escrevi esses dois princípios eu percebi que eles são um pouco
contraditórios, mas fazer o que, é como eu me sinto. Eu sou humano afinal de
contas 😛.)&lt;/p&gt;
&lt;p&gt;Já que eu estava tratando meu blog como um diário fazia sentido para mim tentar
escrever com uma frequência fixa. Eu acabei decidindo escrever uma vez por mês
já que era o suficiente para me manter engajado mas não demais a ponto de me
estressar.&lt;/p&gt;
&lt;p&gt;Eu também acabei estabelecendo o dia 20 do mês como a data alvo. Isso é só uma
diretriz. Eu não me forço nada disso. Se eu não estiver disposto a publicar
nesse mês ou se não conseguir a tempo do dia 20, tudo bem. Mas estabelecer esse
prazo opcional cria uma pressão saudável para que eu vença a preguiça e pense se
alguma coisa interessante que eu queira compartilhar (com meu eu do futuro e com
outras pessoas) aconteceu recentemente.&lt;/p&gt;
&lt;p&gt;Isso tem funcionado muito bem até agora. Eu sinto que eu sempre tive algo
interessante para compartilhar, e de fato eu publiquei todos os meses até o
momento, só me atrasei algumas vezes com relação ao dia 20 (inclusive para este
artigo 😝). Me limitando a um único artigo por mês eu também acabo criando um
acúmulo de artigos para publicar, apesar de geralmente faltar alguma coisa neles
antes de poderem ser publicados (eu tenho pelo menos três nessa situação neste
momento).&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusao"&gt;
&lt;h2&gt;Conclusão&lt;/h2&gt;
&lt;p&gt;Então, em resumo, tem sido uma jornada bem legal até agora. Eu estou feliz de já
ter acumulado 13 artigos (14 contando este) que eu posso rever no futuro e ficar
nostálgico pelas coisas que eu construí ou aprendi, e quem sabe até ter novas
ideias para projetos futuros!&lt;/p&gt;
&lt;p&gt;Eu não sei até quando isso aqui vai continuar, mas por enquanto eu não vejo um
fim 🙂.&lt;/p&gt;
&lt;p&gt;E é isso sobre o lado &amp;quot;humano&amp;quot; do blog. No próximo artigo eu vou falar sobre o
lado técnico: as customizações que eu fiz no blog ao longo desse ano para suprir
minhas necessidades.&lt;/p&gt;
&lt;/div&gt;
</content><category term="2021"/><category term="aniversário-blog"/><category term="blog"/></entry><entry><title>Gerenciando meus pacotes</title><link href="https://nfraprado.net/pt-br/post/gerenciando-meus-pacotes.html" rel="alternate"/><published>2021-05-20T00:00:00-03:00</published><updated>2021-05-20T00:00:00-03:00</updated><author><name>Nícolas F. R. A. Prado</name></author><id>tag:nfraprado.net,2021-05-20:/pt-br/post/gerenciando-meus-pacotes.html</id><summary type="html">&lt;p&gt;Como eu mencionei anteriormente no &lt;a class="reference external" href="/pt-br/post/mudando-para-o-wayland.html"&gt;artigo "Mudando para o Wayland"&lt;/a&gt;, eu recentemente
mudei de computador. Mudanças podem ser bem trabalhosas se você usa um sistema
muito customizado e não tem todas as configurações facilmente disponíveis para
fazer a mudança. Já que eu faço backup dos meus arquivos, que inclui uma …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Como eu mencionei anteriormente no &lt;a class="reference external" href="/pt-br/post/mudando-para-o-wayland.html"&gt;artigo "Mudando para o Wayland"&lt;/a&gt;, eu recentemente
mudei de computador. Mudanças podem ser bem trabalhosas se você usa um sistema
muito customizado e não tem todas as configurações facilmente disponíveis para
fazer a mudança. Já que eu faço backup dos meus arquivos, que inclui uma pasta
&lt;code class="docutils literal"&gt;dotfiles&lt;/code&gt; com todos (ou quase todos) os arquivos de configuração para os
programas que eu uso, eu pensei que seria bem simples.&lt;/p&gt;
&lt;p&gt;Mas durante a mudança, eu percebi que eu não tinha um jeito fácil de ver e
instalar todos os programas que eu uso. Nesse momento eu comecei a pensar em
usar uma distro como o &lt;a class="reference external" href="https://nixos.org/"&gt;NixOS&lt;/a&gt; que usa um gerenciador de pacotes declarativo
justamente para tornar fácil reproduzir um sistema. Mas eu gosto bastante do
&lt;a class="reference external" href="https://archlinux.org/"&gt;Arch Linux&lt;/a&gt;, e sinceramente não existe nenhuma razão para eu não poder ter
essa funcionalidade nele.&lt;/p&gt;
&lt;div class="section" id="gerenciamento-de-pacotes-declarativo"&gt;
&lt;h2&gt;Gerenciamento de pacotes declarativo&lt;/h2&gt;
&lt;p&gt;O objetivo então era ter um gerenciamento de pacotes declarativo para tornar
mudanças futuras mais fáceis. Inclusive, como nesse método os pacotes são
listados em um arquivo, com o versionamento dele nos meus backups eu também
ganho de brinde a possibilidade de voltar para um versão anterior (se eu quiser
reverter algum erro na lista de pacotes, por exemplo).&lt;/p&gt;
&lt;p&gt;Mas ter uma simples lista dos pacotes instalados não é a melhor coisa sobre o
gerenciamento de pacotes declarativo. O maior benefício é que você pode
&lt;em&gt;organizar&lt;/em&gt; essa lista de uma forma que faça sentido para você, reordenando e
agrupando os pacotes conforme necessário. E você pode deixar comentários pelo
arquivo inteiro, que é útil principalmente para registrar a razão de ter
instalado cada pacote, assim você pode usar essas informações para remover os
pacotes que não são mais necessários.&lt;/p&gt;
&lt;p&gt;Uma busca rápida na internet por gerenciadores de pacotes declarativos para o
Arch Linux revelaram o &lt;a class="reference external" href="https://github.com/CyberShadow/aconfmgr"&gt;aconfmgr&lt;/a&gt; e o &lt;a class="reference external" href="https://github.com/rendaw/decpac"&gt;decpac&lt;/a&gt;. O primeiro é mais completo,
contendo também gerenciamento de configurações integrado, mas isso já é demais
para mim, e ele é em bash, então ponto negativo. O segundo é bem simples, você
basicamente tem apenas um arquivo com os comandos para instalar os pacotes e os
pacotes em si em uma sintaxe parecida com JSON. E é em python.&lt;/p&gt;
&lt;p&gt;O decpac também permite instalar do &lt;a class="reference external" href="https://aur.archlinux.org/"&gt;AUR&lt;/a&gt; através do uso da ferramenta &lt;a class="reference external" href="https://github.com/Jguer/yay"&gt;yay&lt;/a&gt;.
Os pacotes do AUR são identificados por uma marcação simples antes do nome do
pacote.&lt;/p&gt;
&lt;p&gt;O decpac é &lt;em&gt;quase&lt;/em&gt; perfeito, mas eu não gostei que ele usa o mesmo arquivo tanto
para a configuração dos comandos quanto para a lista de pacotes, porque isso faz
com que a sintaxe precise ser mais complexa, e ela é similar a JSON mas não
exatamente.&lt;/p&gt;
&lt;p&gt;E foi por isso que eu decidi criar o meu próprio, que eu chamei, bem
criativamente, de pcmn.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="pcmn"&gt;
&lt;h2&gt;pcmn&lt;/h2&gt;
&lt;p&gt;O &lt;a class="reference external" href="https://codeberg.org/nfraprado/pcmn"&gt;pcmn&lt;/a&gt; é bem simples, com menos de 200 linhas de código em python. Ele é
bastante inspirado no decpac com a principal diferença sendo a sintaxe mais
simples. E é bem simples mesmo:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;um pacote por linha (mas linhas sem nenhum pacote são permitidas também, claro)&lt;/li&gt;
&lt;li&gt;tudo depois de um &lt;code class="docutils literal"&gt;#&lt;/code&gt; é um comentário&lt;/li&gt;
&lt;li&gt;opcionalmente o grupo do pacote pode ser escrito entre colchetes antes do nome
do pacote. Quando nenhum grupo é especificado, o padrão é assumido, que
instala do repositório oficial. O único outro grupo é &lt;code class="docutils literal"&gt;aur&lt;/code&gt;, que informa que
o pacote deve ser instalado do AUR usando o yay.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Só existem dois comandos:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;code class="docutils literal"&gt;generate&lt;/code&gt; cria uma nova lista de pacotes com base nos pacotes atualmente
explicitamente instalados (ou seja, as dependências não vão aparecer na lista,
mas não é problema porque o pacman vai resolvê-las).&lt;/li&gt;
&lt;li&gt;&lt;code class="docutils literal"&gt;apply&lt;/code&gt; aplica a lista de pacotes, ou seja, pacotes não listados são
desinstalados e pacotes na lista que não estiverem presentes são instalados.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Um arquivo de configuração separado em JSON pode ser usado para mudar os
comandos usados para listar, instalar e remover os pacotes, tanto para o grupo
padrão quanto para o &lt;code class="docutils literal"&gt;aur&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Eu estou usando ele faz alguns meses já e estou bem satisfeito. Como eu
mencionei, a principal diferença do decpac é que eu separei a configuração e a
lista de pacotes e eu acho que isso fez toda a diferença. A lista de pacotes é
bem limpa e fácil de adicionar itens. Esse é um pedaço da minha:&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="c1"&gt;#&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;# Utilities&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;#&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c1"&gt;# Backups&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;borg&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;borgmatic&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;llfuse&lt;/span&gt; &lt;span class="c1"&gt;# Needed to mount an archive&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c1"&gt;# Notifications&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;libnotify&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;mako&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c1"&gt;# Keyboard (Custom)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;interception&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;caps2esc&lt;/span&gt; &lt;span class="c1"&gt;# Switch Caps with ESC&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;aur&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;interception&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;space2meta&lt;/span&gt; &lt;span class="c1"&gt;# Use Space as Meta&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Toda vez que eu percebo que preciso de algum pacote a mais para alguma coisa eu
adiciono na lista, coloco um comentário sobre o motivo, e rodo &lt;code class="docutils literal"&gt;pcmn apply&lt;/code&gt;
para ele ser instalado. Quando vou instalar um pacote temporariamente só para
fazer algo bem rápido ou algum teste, eu instalo manualmente com o pacman mesmo,
assim da próxima vez que fizer um &lt;code class="docutils literal"&gt;apply&lt;/code&gt; ele já é desinstalado.&lt;/p&gt;
&lt;p&gt;Se você achou o pcmn interessante, dê uma olhada &lt;a class="reference external" href="https://codeberg.org/nfraprado/pcmn"&gt;no repositório dele&lt;/a&gt;
para um pouco mais de informação. Ele também é facilmente instalável &lt;a class="reference external" href="https://aur.archlinux.org/packages/pcmn-git/"&gt;do AUR&lt;/a&gt;,
o que significa que depois de você fazer gerar a lista de pacotes com
&lt;code class="docutils literal"&gt;generate&lt;/code&gt;, &lt;code class="docutils literal"&gt;[aur] pcmn-git&lt;/code&gt; vai aparecer na lista de pacotes que ele mesmo
gerencia. Eu acho isso engraçado por algum motivo 😝.&lt;/p&gt;
&lt;p&gt;E é por isso que eu gosto tanto de python. Com menos de 200 linhas de código eu
escrevi um programa que resolve um problema real que eu estava tendo. &lt;a class="reference external" href="https://xkcd.com/353/"&gt;E também
foi divertido de escrever!&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
</content><category term="2021"/><category term="sysadmin"/><category term="python"/></entry><entry><title>Aprendendo teoria musical escrevendo melodias</title><link href="https://nfraprado.net/pt-br/post/aprendendo-teoria-musical-escrevendo-melodias.html" rel="alternate"/><published>2021-04-20T00:00:00-03:00</published><updated>2021-04-20T00:00:00-03:00</updated><author><name>Nícolas F. R. A. Prado</name></author><id>tag:nfraprado.net,2021-04-20:/pt-br/post/aprendendo-teoria-musical-escrevendo-melodias.html</id><summary type="html">&lt;p&gt;Olha só, primeiro artigo não-técnico no blog 🙂.&lt;/p&gt;
&lt;p&gt;Enfim, eu não cheguei a falar sobre isso por aqui antes, mas eu gosto bastante
de tocar piano. Quando eu era mais novo, eu fiz aulas de violão, mas eu sempre
quis tocar piano, e alguns anos atrás eu finalmente comprei um! Eu …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Olha só, primeiro artigo não-técnico no blog 🙂.&lt;/p&gt;
&lt;p&gt;Enfim, eu não cheguei a falar sobre isso por aqui antes, mas eu gosto bastante
de tocar piano. Quando eu era mais novo, eu fiz aulas de violão, mas eu sempre
quis tocar piano, e alguns anos atrás eu finalmente comprei um! Eu tenho tocado
ele desde então, apesar nunca ter realmente feito aulas e nem praticado
intensamente, mas eu me divirto bastante com ele.&lt;/p&gt;
&lt;p&gt;O que eu mais gosto de fazer é improvisar. Eu acho bem relaxante, e às vezes
surgem coisas interessantes que eu quero desenvolver melhor e transformar em uma
música.&lt;/p&gt;
&lt;p&gt;Outra coisa que eu tenho bastante interesse é teoria musical. Eu acho fascinante
como é possível dissecar as músicas e chegar nos blocos básicos que as formam e
descobrir o que as fazem soar bem. Além disso, eu acho que se eu aprender mais
teoria musical eu vou me divertir mais improvisando e compondo 😀.&lt;/p&gt;
&lt;p&gt;Duas ótimas referências que eu estou usando atualmente para aprender teoria
musical são:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.daveconservatoire.org/"&gt;Dave Conservatoire&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.coursera.org/learn/melodic-forms-simple-harmony"&gt;Curso &amp;quot;Approaching Music Theory: Melodic Forms and Simple Harmony&amp;quot; no
Coursera&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;O Dave Conservatoire é basicamente um Khan Academy para teoria musical e é muito
bom! Eu estou revendo os conceitos básicos e aprendendo algumas coisas novas
nele.&lt;/p&gt;
&lt;p&gt;O curso do Coursera é o que eu estou mais animado, porque ele ensina os blocos
básicos da música de um jeito que é divertido, e analisando músicas existentes.
Além do mais, há tarefas em que você precisa compor sua própria pequena melodia
em estilos diferentes. E é sobre isso que eu quero falar nesse artigo!&lt;/p&gt;
&lt;p&gt;Até o momento no curso, eu tive que escrever melodias em três estilos
diferentes: canto gregoriano, jazz e música tradicional. Eu tentei compor apenas
escrevendo no papel, mas sinceramente acabei dependendo bastante de tocar as
notas no piano para ouvir como cada uma soava.&lt;/p&gt;
&lt;p&gt;Depois que eu acabei cada melodia, eu a transcrevi para uma partitura mais bem
feita usando o &lt;a class="reference external" href="https://musescore.org"&gt;MuseScore&lt;/a&gt;, que é um programa de código aberto sensacional para
escrever partituras. O que é ainda mais incrível é que ele é capaz de tocar suas
partituras usando MIDI! Então para cada melodia eu exportei tanto a partitura
quando o áudio para mostrar aqui.&lt;/p&gt;
&lt;div class="section" id="minhas-melodias"&gt;
&lt;h2&gt;Minhas melodias&lt;/h2&gt;
&lt;div class="section" id="canto-gregoriano"&gt;
&lt;h3&gt;Canto gregoriano&lt;/h3&gt;
&lt;p&gt;A ideia no canto gregoriano era fazer algo usando um conjunto bem limitado de
notas e poucos saltos, mas como você pode ver eu quebrei essa segunda regra,
apesar que eu acho que soou bem.&lt;/p&gt;
&lt;p&gt;Por ser um &lt;a class="reference external" href="https://pt.wikipedia.org/wiki/Canto_gregoriano"&gt;canto gregoriano&lt;/a&gt;, a partitura deveria ter apenas 4 linhas e não
ter compassos, deveria ter um andamento fluido. Mas o MuseScore não foi feito
para isso, então é por isso que tanto o áudio quanto a partitura são mais
&amp;quot;robóticos&amp;quot; do que eu gostaria.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="https://nfraprado.net/pt-br/audio/melodies/gregorian_chant.mp3"&gt;Ouça aqui&lt;/a&gt;&lt;/p&gt;
&lt;img alt="Partitura da melodia de canto gregoriano" src="/images/melodies/gregorian_chant.png" /&gt;
&lt;/div&gt;
&lt;div class="section" id="jazz-lento"&gt;
&lt;h3&gt;Jazz lento&lt;/h3&gt;
&lt;p&gt;Já para o jazz lento deveria haver uma noção de tempo mais estrita, mas ter os
tons mais livres, sendo inclusive encorajada a presença de notas alteradas.&lt;/p&gt;
&lt;p&gt;Para as alterações eu usei um Lá e um Ré naturais, mas como você pode ver, não
há nenhum Lá sustenido para fazer com que os naturais soem fora da armadura de
clave, o que eu só aprendi depois. Isso é definitivamente algo que eu gostaria
de melhorar da próxima vez.&lt;/p&gt;
&lt;p&gt;Eu também acho que eu sem querer coloquei muito uma estética da trilha sonora do
Genshin Impact, que apesar de ser bonita, não é jazz 😛.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="https://nfraprado.net/pt-br/audio/melodies/slow_jazz.mp3"&gt;Ouça aqui&lt;/a&gt;&lt;/p&gt;
&lt;img alt="Partitura da melodia de jazz lento" src="/images/melodies/slow_jazz.png" /&gt;
&lt;/div&gt;
&lt;div class="section" id="musica-tradicional"&gt;
&lt;h3&gt;Música tradicional&lt;/h3&gt;
&lt;p&gt;Por fim, a melodia de música tradicional deveria ser algo mais repetitivo e
fácil de lembrar e cantar junto. Eu fiz em pentatônica porque aprendi que
funcionava bem, e eu também acho que ter um ritmo interessante é importante,
então foi o que eu fiz.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="https://nfraprado.net/pt-br/audio/melodies/folk.mp3"&gt;Ouça aqui&lt;/a&gt;&lt;/p&gt;
&lt;img alt="Partitura da melodia de música tradicional" src="/images/melodies/folk.png" /&gt;
&lt;p&gt;Isso é tudo por enquanto, mas espero compartilhar mais em breve 🙂.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
</content><category term="2021"/><category term="música"/><category term="teoria-musical"/><category term="piano"/></entry><entry><title>Mudando para o Wayland</title><link href="https://nfraprado.net/pt-br/post/mudando-para-o-wayland.html" rel="alternate"/><published>2021-03-21T00:00:00-03:00</published><updated>2021-03-21T00:00:00-03:00</updated><author><name>Nícolas F. R. A. Prado</name></author><id>tag:nfraprado.net,2021-03-21:/pt-br/post/mudando-para-o-wayland.html</id><summary type="html">&lt;p&gt;No meio de janeiro, meu computador decidiu me surpreender, e não de um jeito
bom. Diferentemente de todas as peculiaridades que eu já estava acostumado nele
depois desses 6 anos de uso — teclando falhando, tela piscando, saída de áudio
com mal contato — dessa vez era pior, e nem era um …&lt;/p&gt;</summary><content type="html">&lt;p&gt;No meio de janeiro, meu computador decidiu me surpreender, e não de um jeito
bom. Diferentemente de todas as peculiaridades que eu já estava acostumado nele
depois desses 6 anos de uso — teclando falhando, tela piscando, saída de áudio
com mal contato — dessa vez era pior, e nem era um problema de hardware.&lt;/p&gt;
&lt;p&gt;No mínimo uma vez por dia, o computador travava completamente. A única ação que
surtia algum efeito era segurar o botão de desligar para forçá-lo a desligar.
Não exatamente o ideal...&lt;/p&gt;
&lt;p&gt;Eu tive a idea de tentar entrar nele via SSH usando meu celular (ainda bem que o
Termux existe) e isso funcionou. Eu listei os processos e vi &lt;code class="docutils literal"&gt;xorg-server&lt;/code&gt;
usando 100% da CPU. Eu matei ele, e o computador magicamente voltou à vida. Mas
obviamente, já que eu acabei de matar a interface gráfica, todas minhas
aplicações abertas foram pro saco, e basicamente dava na mesma do que eu ter
reiniciado o computador.&lt;/p&gt;
&lt;p&gt;Depois de três dias aguentando isso, tentando checar logs para depurar o
programa, eu desisti do X.Org.&lt;/p&gt;
&lt;p&gt;Certamente a maioria de vocês sabem sobre isso, e eu com certeza não sou
qualificado para explicar, mas o X.Org, a aplicação que que serviu de base para
as interfaces gráficas no Linux por muito tempo é uma grande bagunça. O seu
código é muito difícil de manter, e a sua arquitetura apresenta problemas de
segurança, tanto que uma nova alternativa surgiu vários anos atrás chamada
&lt;a class="reference external" href="https://wayland.freedesktop.org/"&gt;Wayland&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;A questão é que muitas aplicações hoje ainda dependem do X, o que também
significa que mudar para o Wayland pode ser uma mudança não muito suave. Então
apesar da mudança para o Wayland do ecossistema como um todo parecer inevitável,
as pessoas ainda estão confortáveis o suficiente no X.Org por enquanto.&lt;/p&gt;
&lt;p&gt;E eu também era uma dessas pessoas, até que o próprio X me deu um motivo para
mudar. Esse problema provavelmente poderia ser investigado e consertado, mas
por que gastar tempo com &lt;a class="reference external" href="https://www.phoronix.com/scan.php?page=news_item&amp;amp;px=X.Org-Maintenance-Mode-Quickly"&gt;algo que vai entrar em modo de manutenção em breve&lt;/a&gt;?
Enfim chegou a hora de eu dar uma chance ao Wayland.&lt;/p&gt;
&lt;div class="section" id="fazendo-a-mudanca"&gt;
&lt;h2&gt;Fazendo a mudança&lt;/h2&gt;
&lt;p&gt;Do meu ponto de vista, mudar para o Wayland significava basicamente instalar ele
e mudar todas as aplicações que eu usava que dependiam do X, por equivalentes
que funcionassem no Wayland. A principal sendo a interface gráfica em si, que no
meu caso era o gerenciador de janelas &lt;a class="reference external" href="https://i3wm.org/"&gt;i3&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Felizmente, já existe um gerenciador de janelas bem estabelecido que é
compatível com o i3 para o Wayland, que é o &lt;a class="reference external" href="https://swaywm.org/"&gt;sway&lt;/a&gt;, então essa mudança foi bem
tranquila. Foi só questão de instalar o sway e mudar um pouco o arquivo de
configuração.&lt;/p&gt;
&lt;p&gt;O que ajudou bastante foi que a wiki do sway tem um &lt;a class="reference external" href="https://github.com/swaywm/sway/wiki/i3-Migration-Guide"&gt;guia de migração do i3&lt;/a&gt;,
que não só mostra as principais mudanças necessárias na configuração do i3 para
que ela funcione no sway, mas também uma lista dos programas Wayland
equivalentes aos normalmente usados no X.Org.&lt;/p&gt;
&lt;p&gt;Por exemplo, a definição e troca do layout de teclado, que normalmente é feita
pelo setxkbmap no X, é integrada ao sway então eu apenas usei comandos
&lt;code class="docutils literal"&gt;input&lt;/code&gt; no config do sway para configurar os layouts possíveis e ter um atalho
para alternar entre eles.&lt;/p&gt;
&lt;p&gt;Ainda a respeito do teclado, eu me acostumei bastante a usar meu Caps Lock para
duas funções distintas: ele funciona como Esc quando é pressionado sozinho, mas
como Control quando é segurado enquanto outra tecla é apertada. Eu usava
setxkbmap junto com xcape para fazer isso, mas claramente eles não eram opções
no Wayland.&lt;/p&gt;
&lt;p&gt;Quem me salvou foi o &lt;a class="reference external" href="https://gitlab.com/interception/linux/tools"&gt;Interception Tools&lt;/a&gt;, que permite que o mesmo seja feito
independente de estar no X ou no Wayland. Ele por si só apenas provê a
estrutura, mas essa função de Control e Esc no Caps Lock que mencionei pode ser
facilmente configurada usando o plugin oficial &lt;a class="reference external" href="https://gitlab.com/interception/linux/plugins/caps2esc"&gt;caps2esc&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Ah, e ele também pode ser combinado com outro plugin, &lt;a class="reference external" href="https://gitlab.com/interception/linux/plugins/space2meta"&gt;space2meta&lt;/a&gt;, para usar a
barra de espaço como espaço quando apertada, mas como a tecla Meta (o
modificador no i3/sway) quando segurada. Eu já tinha experimentado isso no X
usando o xcape mas eu acabava disparando atalhos do i3 sem querer, então eu
parei de usar. Mas já que o space2meta é mais esperto e apenas manda a tecla no
evento da tecla solta, é bem usável e vale a pena pelo maior conforto.&lt;/p&gt;
&lt;p&gt;Uma pequena tangente: Se o caps2esc e o space2meta parecem loucura para
você, eu realmente aconselho experimentar. Principalmente o caps2esc, já que
ele não tem nenhuma desvantagem, sinceramente. Pense bem, você realmente usa
tanto o Caps Lock? Não faz sentido mantê-lo na fileira central do teclado. Já o
Esc e o Control são muito úteis, ainda mais se você usa Vim. Bom, voltando...&lt;/p&gt;
&lt;p&gt;E o &lt;a class="reference external" href="https://github.com/davatorium/rofi"&gt;rofi&lt;/a&gt;, um menu de seleção, que eu uso para várias coisas diferentes? Também
depende do X. Existe uma alternativa para o Wayland chamado &lt;a class="reference external" href="https://hg.sr.ht/~scoopta/wofi"&gt;wofi&lt;/a&gt;, mas eu não
gostei dele. Senti que ele é um pouco lento e não sou fã de ele ser GTK (o ícone
de lupa do lado do campo de entrada me incomoda bastante). Eu só quero um
retângulo de um única cor com um título, caixa de entrada e as opções filtradas
destacadas abaixo, e o rofi faz isso muito melhor. Por sorte, uma alma corajosa
criou um &lt;a class="reference external" href="https://github.com/lbonn/rofi"&gt;fork do rofi para o Wayland&lt;/a&gt; e ele funciona sem nenhum problema.
Praticamente...&lt;/p&gt;
&lt;p&gt;Infelizmente a funcionalidade de troca de janela não funciona nele. E eu acho
ela muito útil para pular diretamente para uma aplicação aberta, caso contrário
eu preciso passar por todos os workspaces procurando por ela. Mas foi bastante
fácil encontrar um script de troca de janela na internet, mas para o wofi, então
eu apenas mudei ele um pouco para funcionar com o rofi e assim consegui &lt;a class="reference external" href="https://gist.github.com/nfraprado/484a0dfe60c49b7d2e9dfb45c3c4a4b5"&gt;meu
script de troca de janela&lt;/a&gt;. E agora sim, um rofi perfeito no Wayland.&lt;/p&gt;
&lt;p&gt;Capturar a tela com o maim também não era mais possível. A alternativa é o
&lt;a class="reference external" href="https://github.com/emersion/grim"&gt;grim&lt;/a&gt;, e um programa separado, &lt;a class="reference external" href="https://github.com/emersion/slurp"&gt;slurp&lt;/a&gt;, para permitir que uma região ou janela
seja selecionada para a captura. Como um bônus interessante, como o README do
grim mostra, é bem simples usá-lo, junto com o slurp e imagemagick, para obter a
cor do pixel embaixo do mouse, então eu também configurei esse modo. Minha
tentativa anterior de fazer isso com o maim falhou, então esse foi um bom bônus
de ter mudado para o Wayland.&lt;/p&gt;
&lt;p&gt;xclip obviamente também não funciona no Wayland. Então para continuar tendo um
clipboard funcional, eu precisei instalar &lt;a class="reference external" href="https://github.com/bugaevc/wl-clipboard"&gt;wl-clipboard&lt;/a&gt;. Mas isso não foi o
suficiente para o Vim. Nele, operações no clipboard são feitas através do
registrador &lt;code class="docutils literal"&gt;+&lt;/code&gt;, por exemplo &lt;code class="docutils literal"&gt;&amp;quot;+yy&lt;/code&gt; para copiar a linha atual para o
clipboard, mas isso não funciona com o clipboard do Wayland. Eu cheguei muito
perto de instalar o Neovim já que aparentemente isso funciona por padrão nele,
mas eu encontrei o plugin de Vim &lt;a class="reference external" href="https://github.com/jasonccox/vim-wayland-clipboard"&gt;vim-wayland-clipboard&lt;/a&gt; que funciona
perfeitamente.&lt;/p&gt;
&lt;p&gt;Uma coisa que eu não esperava que não funcionasse de cara era o compartilhamento
de tela. Felizmente &lt;a class="reference external" href="https://wiki.archlinux.org/index.php/PipeWire#WebRTC_screen_sharing"&gt;a página do PipeWire na Archwiki&lt;/a&gt; fornece instruções de
como fazer funcionar, e é bem simples: apenas configurar uma variável de
ambiente e instalar &lt;a class="reference external" href="https://github.com/emersion/xdg-desktop-portal-wlr"&gt;xdg-desktop-portal-wlr&lt;/a&gt; e &lt;code class="docutils literal"&gt;pipewire-media-session&lt;/code&gt;. Dito
isso, eu tive um &lt;a class="reference external" href="https://gitlab.freedesktop.org/pipewire/pipewire/-/issues/768"&gt;problema&lt;/a&gt; depois de uma atualização no PipeWire, mas ao que
tudo indica já foi resolvido.&lt;/p&gt;
&lt;p&gt;Eu também mudei meu daemon de notificação do dunst para o &lt;a class="reference external" href="https://github.com/emersion/mako"&gt;mako&lt;/a&gt;. Mas pelo que
vi, a partir da versão 1.6.0, o dunst também suporta Wayland, apesar de ainda
não ter sido empacotado no Arch Linux. Mas eu sinceramente também estou
satisfeito com o mako, então acho que vou continuar com ele.&lt;/p&gt;
&lt;p&gt;Algumas outras mudanças que eu acho que não precisam de nenhum comentário
adicional:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://github.com/haikarainen/light"&gt;light&lt;/a&gt; ao invés de xbacklight para controlar o brilho da tela&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://gitlab.com/chinstrap/gammastep"&gt;gammastep&lt;/a&gt; ao invés de redshift para tornar a temperatura da tela mais
agradável de noite&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://github.com/eXeC64/imv"&gt;imv&lt;/a&gt; ao invés de sxiv como o visualizador de imagens&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;E finalmente, é importante mencionar o Xwayland. Ele é a aplicação que te
permite rodar aplicações X no Wayland. Não funciona para tudo (caso contrário eu
não teria mudado todas as aplicações anteriores), mas por exemplo para aplicações
Electron e para a Steam e jogos funcionou perfeitamente até agora, tanto que é
difícil saber que eles não são nativos no Wayland, o que é incrível. Se o
Wayland quer ser o futuro, ele precisa permitir que as aplicações X atuais
continuem rodando nele enquanto elas não são devidamente portadas para o
Wayland.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="plot-twist"&gt;
&lt;h2&gt;Plot twist&lt;/h2&gt;
&lt;p&gt;Depois de todas essas mudanças eu estava totalmente estabelecido no Wayland, mas
ainda sim aconteciam alguns glitches gráficos. De tempo em tempo, as aplicações
começavam a piscar de jeitos estranhos e nada que eu fizesse fazia com que
parassem, a não ser, claro, fechar o Wayland. Então eu estava essencialmente de
volta ao ponto de partida, mas um pouco melhor já que nesse caso o computador
não travava completamente, então eu pelo menos tinha chance de salvar tudo antes
de reiniciar.&lt;/p&gt;
&lt;p&gt;Felizmente, no meio-tempo eu recebi um computador novo da empresa em que estou
trabalhando, o que foi ótimo dadas as peculiaridades do meu computador antigo. E
já que eu já tinha me dado o trabalho de fazer a transição para o Wayland, eu
instalei sway nesse novo computador também. E tudo funcionou perfeitamente. Sem
problema algum.&lt;/p&gt;
&lt;p&gt;Então, qual é a conclusão dessa história? O problema do congelamento/glitch
gráfico era algo relacionado ao hardware do meu computador antigo e não no X em
si, mas pelo menos isso me deu o empurrão que eu precisava para finalmente
migrar para o Wayland. Sim, eu provavelmente poderia ter continuado no X no
computador novo, mas isso seria apenas postergar o inevitável. O importante é
que agora tudo funciona, e eu me sinto muito mais confortável em depurar e
reportar problemas com a nova interface gráfica, dado que ela é bem mantida,
caso eles aconteçam no futuro.&lt;/p&gt;
&lt;/div&gt;
</content><category term="2021"/><category term="wayland"/></entry><entry><title>Ledsticker: Meu primeiro projeto holístico de SW + HW</title><link href="https://nfraprado.net/pt-br/post/ledsticker-meu-primeiro-projeto-holistico-de-sw-hw.html" rel="alternate"/><published>2021-02-20T00:00:00-03:00</published><updated>2021-02-20T00:00:00-03:00</updated><author><name>Nícolas F. R. A. Prado</name></author><id>tag:nfraprado.net,2021-02-20:/pt-br/post/ledsticker-meu-primeiro-projeto-holistico-de-sw-hw.html</id><summary type="html">&lt;p&gt;Era julho de 2019. Eu estava fazendo aula de Laboratório de Sistemas Embarcados
e tinha que fazer um projeto final de minha escolha. Eu tive a ideia de fazer
uma espécie de Guitar Hero usando um teclado como entrada, uma matriz de LED 8x8
para mostrar as notas e alto-falantes …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Era julho de 2019. Eu estava fazendo aula de Laboratório de Sistemas Embarcados
e tinha que fazer um projeto final de minha escolha. Eu tive a ideia de fazer
uma espécie de Guitar Hero usando um teclado como entrada, uma matriz de LED 8x8
para mostrar as notas e alto-falantes para tocar o som das notas. O problema era
que a faculdade não disponibilizava matriz de LED nem alto-falantes, então eu
decidi comprá-los, com a desculpa de que eu usaria eles depois para os meus
projetos pessoais também.&lt;/p&gt;
&lt;p&gt;Esse projeto deu certo, e o resultado pode ser visto no &lt;a class="reference external" href="https://www.youtube.com/watch?v=JIfGUSdEFQ0"&gt;YouTube&lt;/a&gt;. Mas tendo
finalizado ele, eu comecei a pensar o que eu iria fazer com a matriz de LED.
Eventualmente, eu tive uma ideia que era tão legal que não tinha como não fazer.&lt;/p&gt;
&lt;p&gt;Eu gostava de grudar adesivos que achasse legais na tampa do meu notebook, e eu
tinha muitos deles, mas se você parar para pensar, adesivos não são tão
interessantes. Então e se eu usasse a matriz de LED como um adesivo? Eu poderia
grudá-la na tampa do notebook e conectá-la à porta USB tanto para fornecer
energia quanto permitir que ela pudesse ser controlada. Eu seria capaz de
programar qualquer coisa que eu quisesse que fosse mostrada na matriz.&lt;/p&gt;
&lt;div class="section" id="prototipagem"&gt;
&lt;h2&gt;Prototipagem&lt;/h2&gt;
&lt;p&gt;O primeiro passo para tornar isso realidade era construir um protótipo. Eu já
tinha o módulo da matriz de LED, mas ele recebia os dados através de um
barramento SPI ao invés do USB que eu queria usar. Eu comecei a procurar na
internet por um circuito que convertesse USB para SPI e encontrei o CI MCP2210.
Eu comprei alguns deles, mas também comprei sua versão em placa de
desenvolvimento, com o CI na placa pronto para uso, para que o processo de
prototipagem inicial fosse mais fácil.&lt;/p&gt;
&lt;p&gt;Então eu procurei por um programa que implementasse a interface com o MCP2210 e
encontrei &lt;a class="reference external" href="https://github.com/kerrydwong/MCP2210-Library"&gt;MCP2210-Library&lt;/a&gt;. Eu comecei a escrever o meu próprio programa para
inicializar o CI responsável por controlar a matriz de LED, o MAX7219, (presente
no módulo de matriz de LED) através da chamada das funções dessa biblioteca para
enviar os dados SPI através do USB (os quais o MCP2210 depois encaminhava
através do barramento SPI para o MAX7219).&lt;/p&gt;
&lt;p&gt;Nesse ponto eu tinha o mínimo necessário para verificar a viabilidade do
projeto: os componentes de hardware necessários nos próprios módulos prontos
para uso e um software mínimo capaz de interagir com os CIs e desenhar um padrão
fixo na matriz de LED. Eu conectei o módulo USB-para-SPI no computador e módulo
da matriz de LED e rodei o meu programa:&lt;/p&gt;
&lt;img alt="Módulos de USB-para-SPI e da matriz de LED conectados. Alguns LEDs estão ligados mostrando que está funcionando." src="/images/ledsticker/modules.png" /&gt;
&lt;p&gt;Tendo confirmado que o projeto de fato era viável, eu comecei a avançar nos
passos necessários para torná-lo usável ao usuário final. A primeira coisa era
tornar simples o controle da matriz de LED para os propósitos do projeto. Foi
nesse ponto que eu defini o conceito de &lt;em&gt;adesivo&lt;/em&gt;, que é central ao projeto.&lt;/p&gt;
&lt;div class="section" id="adesivos"&gt;
&lt;h3&gt;Adesivos&lt;/h3&gt;
&lt;p&gt;Eu queria que fosse possível mostrar três coisas diferentes na matriz de LED:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Uma &amp;quot;imagem&amp;quot; estática, ou seja, ligar um padrão estático de LEDs.&lt;/li&gt;
&lt;li&gt;Uma &amp;quot;imagem&amp;quot; animada, ou seja, mostrar uma sequência de padrões, cada um
depois de um certo atraso&lt;/li&gt;
&lt;li&gt;Uma &amp;quot;imagem&amp;quot; animada mas que fosse gerada dinamicamente da saída de um
programa, para que eu pudesse mostrar uma simulação do &lt;a class="reference external" href="https://pt.wikipedia.org/wiki/Jogo_da_vida"&gt;Jogo da Vida&lt;/a&gt;, por
exemplo.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Para permitir os dois primeiros casos de uso, eu defini o conceito de um adesivo
estático. Ele consiste em um arquivo de texto contendo uma sequência de comandos
que determinam o que é mostrado na matriz. Existem comandos para ligar e
desligar pixels individuais, colunas ou linhas inteiras, ou a tela inteira,
também para atualizar a tela, etc.&lt;/p&gt;
&lt;p&gt;Para possibilitar o terceiro caso de uso, eu defini o adesivo dinâmico, que é
qualquer programa, programado em qualquer linguagem, que escreve esses comandos
de adesivo em sua saída. Dessa forma, uma linguagem de programação pode ser
usada para gerar um adesivo mais complexo e dinâmico.&lt;/p&gt;
&lt;p&gt;A ideia portanto é que o usuário executaria o programa &lt;code class="docutils literal"&gt;ledsticker&lt;/code&gt; e passaria
como parâmetro um adesivo estático ou dinâmico. Se fosse estático, os comandos
seriam lidos e a matriz de LED atualizada de acordo. Se fosse dinâmico,
ele seria executado e sua saída usada como um adesivo estático da mesma forma.&lt;/p&gt;
&lt;p&gt;Para que tudo isso funcionasse, eu precisava tanto definir os comandos dos
adesivos, como também implementar a leitura desses comandos pelo meu programa.
Como um exemplo dos comandos, o mais simples é &lt;code class="docutils literal"&gt;on R C&lt;/code&gt;, que liga o LED da
linha &lt;code class="docutils literal"&gt;R&lt;/code&gt; e coluna &lt;code class="docutils literal"&gt;C&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Finalmente, para confirmar que esses comandos eram o suficiente, e também por
diversão, eu implementei alguns adesivos: um único quadro com a cara do Creeper,
um Tetris animado, e uma simulação gerada dinamicamente do Jogo da Vida.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="hardware"&gt;
&lt;h3&gt;Hardware&lt;/h3&gt;
&lt;p&gt;Nesse ponto eu já tinha o software totalmente operacional, então eu queria
começar a avançar no lado do hardware. Eu estava usando módulos separados para a
matriz de LED e para o USB-para-SPI, mas o objetivo mesmo era ter isso tudo em
uma única e pequena placa de circuito impresso (PCB), para que fosse conveniente
anexá-la à tampa do notebook.&lt;/p&gt;
&lt;p&gt;Mas antes de começar a fazer a PCB, eu precisava ter certeza sobre o circuito.
Eu tinha dois módulos separados que eu sabia que funcionavam, então eu usei eles
como referência e li os datasheets do MAX7219 e do MCP2210 para criar o
esquemático do circuito no &lt;a class="reference external" href="https://kicad.org"&gt;KiCad&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Ao final, meu esquemático ficou com os dois CIs principais, MAX7219 e MCP2210,
além de outros componentes passivos necessários para o funcionamento deles
(resistores, capacitores e um cristal), bem como a matriz de LED e um conector
USB mini.&lt;/p&gt;
&lt;p&gt;O símbolo de esquemático (e footprint) da matriz de LED (modelo 1088AS) em
especial eu precisei criar por contra própria, já que ele não existia. Eu segui
a &lt;a class="reference external" href="https://www.youtube.com/watch?v=vaCVh2SAZY4&amp;amp;list=PLEBQazB0HUyR24ckSZ5u05TZHV9khgA1O"&gt;série de introdução ao KiCad da DigiKey&lt;/a&gt; para isso.&lt;/p&gt;
&lt;p&gt;Com o circuito decidido, eu soldei todos os componentes e conexões em uma placa
universal (teria sido mais fácil usar uma protoboard, mas eu não tinha uma boa)
para testar. A parte mais difícil foi que o MCP2210 é um dispositivo de montagem
superficial (SMD), e SMDs não são feitos para serem soldados em uma placa
universal. O que eu fiz foi comprar uma PCB adaptadora de SMD com o tamanho
correto de pinos para que eu conseguisse soldar o CI nela e soldá-la na minha
placa. Tudo isso ficou assim:&lt;/p&gt;
&lt;img alt="Placa protótipo para o Ledsticker. Todos os componentes e conexões estão soldados." src="/images/ledsticker/prototype.jpg" /&gt;
&lt;p&gt;Depois de testar essa placa com alguns adesivos e concluir que estava
funcionando bem, a última coisa que restava era fazer a PCB.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="pcb"&gt;
&lt;h2&gt;PCB&lt;/h2&gt;
&lt;p&gt;Eu nunca tinha feito uma PCB, e a ideia de finalmente aprender a fazer uma foi
uma das coisas que me motivou a fazer esse projeto para começo de conversa.&lt;/p&gt;
&lt;p&gt;Eu aprendi praticamente tudo seguindo a &lt;a class="reference external" href="https://www.youtube.com/watch?v=vaCVh2SAZY4&amp;amp;list=PLEBQazB0HUyR24ckSZ5u05TZHV9khgA1O"&gt;série de introdução ao KiCad da
DigiKey&lt;/a&gt; que eu já mencionei. Há muitos passos diferentes em fazer uma PCB, mas
eles não chegam a ser difíceis, só trabalhosos às vezes. Posicionar os
componentes de uma forma compacta tendo em mente as ligações que precisariam ser
feitas foi bastante complicado, mas foi um desafio divertido.&lt;/p&gt;
&lt;p&gt;Depois que finalizei o projeto no KiCad, eu encomendei a PCB da OSHPark. Depois
de 3 longos meses esperando (por conta de demoras no transporte de navio devido
à pandemia), a placa finalmente chegou.&lt;/p&gt;
&lt;p&gt;Depois de tanto tempo esperando pela placa, eu não perdi tempo para soldar.
Peguei todos os componentes e soldei tudo no mesmo dia. Aqui estão todos os
componentes junto com a PCB:&lt;/p&gt;
&lt;img alt="PCB customizada para o Ledsticker no topo. Todos os componentes que serão soldados em baixo." src="/images/ledsticker/components_pcb.jpg" /&gt;
&lt;p&gt;Metade do caminho (era o que eu pensava):&lt;/p&gt;
&lt;img alt="PCB com apenas alguns dos componentes soldados." src="/images/ledsticker/partially_soldered_pcb.jpg" /&gt;
&lt;p&gt;Soldar os componentes SMD (MCP2210 e o conector USB mini) foi muito mais difícil
do que o restante. O conector USB mini em particular foi praticamente
impossível. Os contatos na PCB para os seus 5 pinos eram praticamente do mesmo
tamanho que os pinos do conector em si, e não eram muito acessíveis para o ferro
de solda. Mas depois de múltiplas tentativas, eu finalmente consegui. Aqui está
a placa final:&lt;/p&gt;
&lt;img alt="PCB com todos os componentes soldados." src="/images/ledsticker/final_board.jpg" /&gt;
&lt;p&gt;Nem preciso dizer que foi incrível ver minha própria PCB. Tem algo mágico em
segurar algo que você mesmo projetou, que escrever software nunca poderia
proporcionar. Ver o circuito todo muito bem organizado na PCB também deu uma
impressão muito mais robusta e profissional (veja de novo a placa de
prototipagem como comparação...).&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="resultados"&gt;
&lt;h2&gt;Resultados&lt;/h2&gt;
&lt;p&gt;Com tudo finalmente pronto, a placa e o programa, era hora de brincar. O gif
seguinte mostra tanto a linha de comando usada para carregar o adesivo quanto a
placa sendo atualizada. Três adesivos são carregados em sequência. Primeiro uma
cara de Creeper estática, depois uma animação de Tetris, e finalmente uma
simulação do Jogo da Vida gerada por um programa em python &amp;quot;em tempo real&amp;quot; &lt;a class="footnote-reference" href="#footnote-1" id="footnote-reference-1"&gt;[1]&lt;/a&gt;:&lt;/p&gt;
&lt;img alt="GIF mostrando a linha de comando e a placa com os LEDs atualizando simultaneamente. Três adesivos são mostrados em sequência." src="/images/ledsticker/ledsticker.gif" /&gt;
&lt;p&gt;E isso marca o projeto como concluído! Calma, mas e a parte de grudar a placa na
tampa do notebook? Bom, quando eu comecei esse projeto, eu tinha essa intenção
por um motivo social: conforme eu andasse com o meu notebook pela universidade,
pessoas iriam me perguntar sobre ele, então eu teria uma desculpa para ter
conversas nerds sobre a placa e seria um ótimo começador de conversas. Mas com a
pandemia, eu sempre estou em casa, então fazer isso não só seria inútil como aí
nem mesmo &lt;em&gt;eu&lt;/em&gt; veria a placa. E é por isso que eu estou adiando a anexação para
depois que eu voltar a frequentar espaços públicos de novo. Mas só para dar uma
ideia de como ficaria, eu grudei ela com fita adesiva só para essa foto:&lt;/p&gt;
&lt;img alt="Tampa do notebook com adesivos comuns cobrindo a maior parte e o Ledsticker no meio mostrando o adesivo de cara de Creeper." src="/images/ledsticker/notebook.jpg" /&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusao"&gt;
&lt;h2&gt;Conclusão&lt;/h2&gt;
&lt;p&gt;Esse foi de longe meu projeto preferido. Realmente foi um projeto holístico,
onde eu projetei tanto o software quanto o hardware e precisei pensar em muitos
aspectos diferentes. No lado do software eu precisei criar comandos para prover
uma boa interface com o usuário além de implementar o programa como um todo (a
não ser pela comunicação com o MCP2210). No lado do hardware eu investiguei
datasheets para projetar o circuito, organizei tudo para ser compacto e fiz
minha primeira PCB!&lt;/p&gt;
&lt;p&gt;Se você achou o projeto interessante, eu te convido a fazer sua própria placa
ledsticker e criar seus próprios adesivos! Tudo é aberto. Você pode encontrar o
programa de linha de comando junto com informações detalhadas sobre ele em
&lt;a class="reference external" href="https://codeberg.org/nfraprado/ledsticker"&gt;ledsticker&lt;/a&gt;. O esquemático da placa junto com a descrição do hardware podem ser
encontrados em &lt;a class="reference external" href="https://codeberg.org/nfraprado/ledsticker-hw"&gt;ledsticker-hw&lt;/a&gt;. Você também pode &lt;a class="reference external" href="https://oshpark.com/shared_projects/MHbtuh9G"&gt;encomendar a PCB diretamente da
OSHPark&lt;/a&gt; caso queira (eu não vou receber nada por isso). Ah, e se você fizer a
placa ou um adesivo, eu adoraria saber! 😃 (Você pode encontrar meu email na
página Sobre).&lt;/p&gt;
&lt;img alt="Ledsticker with a Glider sticker. The Glider is walking diagonally on loop." src="/images/ledsticker/glider.gif" /&gt;
&lt;table class="docutils footnote" frame="void" id="footnote-1" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;&lt;a class="fn-backref" href="#footnote-reference-1"&gt;[1]&lt;/a&gt;&lt;/td&gt;&lt;td&gt;Não é realmente em tempo real. O programa do adesivo dinâmico é executado
o mais rápido possível, preenchendo o buffer de entrada do &lt;code class="docutils literal"&gt;ledsticker&lt;/code&gt;,
que é então consumido quadro a quadro muito mais lentamente, dependendo do
FPS que foi configurado.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
</content><category term="2021"/><category term="projeto"/><category term="eletrônica"/><category term="cli"/><category term="c"/></entry><entry><title>Organização além do Taskwarrior</title><link href="https://nfraprado.net/pt-br/post/organizacao-alem-do-taskwarrior.html" rel="alternate"/><published>2021-01-20T00:00:00-03:00</published><updated>2021-01-20T00:00:00-03:00</updated><author><name>Nícolas F. R. A. Prado</name></author><id>tag:nfraprado.net,2021-01-20:/pt-br/post/organizacao-alem-do-taskwarrior.html</id><summary type="html">&lt;p&gt;No artigo anterior dessa série, eu falei sobre todas as minhas customizações no
Taskwarrior e no VIT, e meu fluxo de trabalho com eles, que me permite organizar
e concluir minhas tarefas. No entanto, apenas tarefas não são o suficiente para
se organizar.&lt;/p&gt;
&lt;p&gt;Outro componente crucial na organização é ter …&lt;/p&gt;</summary><content type="html">&lt;p&gt;No artigo anterior dessa série, eu falei sobre todas as minhas customizações no
Taskwarrior e no VIT, e meu fluxo de trabalho com eles, que me permite organizar
e concluir minhas tarefas. No entanto, apenas tarefas não são o suficiente para
se organizar.&lt;/p&gt;
&lt;p&gt;Outro componente crucial na organização é ter um calendário. Ele te permite
estar ciente de tarefas e eventos que são sensíveis ao tempo (por exemplo, com
data de entrega), e também tomar decisões bem informadas no momento de marcar
novos eventos. Claro que nada disso é novo, e é até parte do &lt;a class="reference external" href="https://pt.wikipedia.org/wiki/Getting_Things_Done"&gt;GTD&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Mas algo que não é parte do GTD, e que eu senti falta, é algo para estabelecer
uma rotina. O GTD é um ótimo sistema para acompanhar as diferentes tarefas de
cada projeto na sua vida, e para concluí-los, mas ele não tem a menor
preocupação em reservar diferentes porções do seu dia ou semana para fazer os
básicos (como comer), fazer algo regularmente (como praticar piano), ou avançar
nas tarefas gerais. Ele apenas organiza o que fazer e &lt;a class="reference external" href="https://nfraprado.net/pt-br/post/deteccao-de-contexto-automatica-para-o-taskwarrior.html"&gt;em quais contextos&lt;/a&gt;, mas
não exatamente quando, o que pode deixar a desejar, quando se está tentando
&lt;a class="reference external" href="https://waitbutwhy.com/2016/10/100-blocks-day.html"&gt;aproveitar bem o tempo&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Portanto, além de ter o VIT como meu organizador central de tarefas, meu sistema
também precisa&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;de um calendário decente, de alguma forma integrado ao VIT para também mostrar
os prazos de tarefas, além dos compromissos normais&lt;/li&gt;
&lt;li&gt;um jeito de configurar uma rotina, e de ser constantemente lembrado dela&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Vamos ver sobre cada um deles.&lt;/p&gt;
&lt;div class="section" id="calendario-usando-calcurse"&gt;
&lt;h2&gt;Calendário usando calcurse&lt;/h2&gt;
&lt;p&gt;Antes de mais nada, o próprio Taskwarrior na verdade tem um calendário, que pode
ser visto com &lt;code class="docutils literal"&gt;task calendar&lt;/code&gt;, mas sinceramente ele é inútil. Só é possível
ver quais dias tem tarefas, mas não quais são essas tarefas.&lt;/p&gt;
&lt;p&gt;Eu queria um calendário que me desse uma boa visão geral dos compromissos
mensais e semanais, que fosse leve (de preferência de terminal), e customizável
o suficiente para ser integrado ao VIT. Eu acabei usando o &lt;a class="reference external" href="https://www.calcurse.org/"&gt;calcurse&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Agora, a grande questão era: Como eu posso ter minhas tarefas aparecendo no
calcurse? Bom, já que o calcurse usa um arquivo de texto com uma sintaxe simples
para armazenar todos os compromissos, eu só precisava de um script que lesse
todas as tarefas do Taskwarrior e escrevesse na saída os compromissos usando a
sintaxe do calcurse. E esse foi o script que eu escrevi:&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="ch"&gt;#!/bin/python&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;tasklib&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TaskWarrior&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;sys&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;tw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TaskWarrior&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/home/nfraprado/.task/data'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c1"&gt;# Parse appointments&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;apts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tw&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'(status:pending or status:waiting or status:completed)'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'cal'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;apt&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;apts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;apt&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'scheduled'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Apt '&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;apt&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;' has no sched date!&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;    &lt;span class="n"&gt;summary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;apt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hour&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;minute&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="n"&gt;start_fmt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;strftime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;%m/&lt;/span&gt;&lt;span class="si"&gt;%d&lt;/span&gt;&lt;span class="s2"&gt;/%Y&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;start_fmt&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; [1] &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;summary&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="n"&gt;start_fmt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;strftime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;%m/&lt;/span&gt;&lt;span class="si"&gt;%d&lt;/span&gt;&lt;span class="s2"&gt;/%Y &amp;#64; %H:%M&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;apt&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'due'&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="n"&gt;end_fmt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;apt&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'due'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;strftime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;%m/&lt;/span&gt;&lt;span class="si"&gt;%d&lt;/span&gt;&lt;span class="s2"&gt;/%Y &amp;#64; %H:%M&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="n"&gt;end_fmt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;start_fmt&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;        &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;start_fmt&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; -&amp;gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;end_fmt&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;|&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;summary&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c1"&gt;# Parse due dates for next actions and projects&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;tasks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tw&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'(status:pending or status:waiting) and (type:next or '&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;                       &lt;span class="s1"&gt;'type:objective or type:standby)'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;date_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;label&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="s1"&gt;'due'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Prazo final: &amp;quot;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;                             &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'scheduled'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Prazo inicial: &amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)]:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;date_type&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="c1"&gt;# Skip tasks with no date&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;date_type&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;        &lt;span class="n"&gt;proj&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Projeto: &amp;quot;&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'type'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;objective&amp;quot;&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;        &lt;span class="n"&gt;summary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;label&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;proj&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hour&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;minute&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="n"&gt;start_fmt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;strftime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;%m/&lt;/span&gt;&lt;span class="si"&gt;%d&lt;/span&gt;&lt;span class="s2"&gt;/%Y&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;start_fmt&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; [1] &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;summary&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="n"&gt;start_fmt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;strftime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;%m/&lt;/span&gt;&lt;span class="si"&gt;%d&lt;/span&gt;&lt;span class="s2"&gt;/%Y &amp;#64; %H:%M&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="n"&gt;end_fmt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;start_fmt&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;start_fmt&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; -&amp;gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;end_fmt&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;|&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;summary&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Esse script basicamente define quais as tarefas que vão ser mostradas no
calendário e em qual formato. Primeiro, tem as tarefas &lt;code class="docutils literal"&gt;cal&lt;/code&gt;, que, se você
lembrar do artigo passado, são meus compromissos, e são a principal coisa a ser
mostrada no calendário. Cada uma delas é convertida em uma entrada no calcurse,
com a data &lt;code class="docutils literal"&gt;scheduled&lt;/code&gt; usada como a hora de início do compromisso, e a data
&lt;code class="docutils literal"&gt;due&lt;/code&gt; como a data de término. O texto do compromisso é simplesmente o texto de
descrição da tarefa.&lt;/p&gt;
&lt;p&gt;O outro caso são as tarefas normais, que aparecem não como um evento contínuo no
calendário, mas sim uma entrada para a data de início, para me mostrar quando eu
posso começar a fazer a tarefa, e outra para a data de término, para me mostrar
até quando eu preciso concluí-la. Além disso, essas sim possuem identificadores
a mais no texto de descrição do compromisso, para diferenciá-los dos
compromissos normais. Para isso, o script adiciona &amp;quot;Prazo inicial: &amp;quot; à descrição
da data de início e &amp;quot;Prazo final: &amp;quot; à descrição da data de término. Por fim, se
a tarefa é do tipo &lt;code class="docutils literal"&gt;objective&lt;/code&gt;, ela além disso tem &amp;quot;Projeto: &amp;quot; adicionado à
sua descrição no calendário, significando que essa data é relevante para o
projeto como um todo e não a uma única próxima ação.&lt;/p&gt;
&lt;p&gt;Inicialmente, era isso. Eu mapeei uma tecla no VIT para rodar esse programa e
então recarregava os compromissos do calcurse com &lt;code class="docutils literal"&gt;R&lt;/code&gt;. Dessa forma, eu
precisava pressionar duas teclas em duas janelas diferentes para ver o
calendário atualizado.&lt;/p&gt;
&lt;p&gt;Depois de um tempo, eu descobri que o calcurse também suporta &lt;em&gt;hooks&lt;/em&gt; (assim
como o Taskwarrior, como mostrado no artigo anterior) e adicionei um &lt;em&gt;hook&lt;/em&gt;
&lt;code class="docutils literal"&gt;pre-load&lt;/code&gt; com o seguinte:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;taskwarrior-task2cal&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;/home/nfraprado/.calcurse/apts
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;O que significa que quando eu pressiono &lt;code class="docutils literal"&gt;R&lt;/code&gt; no calcurse, ele automaticamente
roda meu script para exportar as tarefas para o arquivo do calcurse, e portanto
eu agora preciso apertar uma única tecla no calcurse para ver meu calendário
atualizado! 🙂&lt;/p&gt;
&lt;p&gt;O gif a seguir mostra tarefas &lt;code class="docutils literal"&gt;next&lt;/code&gt; e &lt;code class="docutils literal"&gt;cal&lt;/code&gt; sendo adicionadas no
Taskwarrior e automaticamente aparecendo no calcurse:&lt;/p&gt;
&lt;img alt="Tarefas sendo mostradas no Taskwarrior e aparecendo automaticamente no calcurse" src="/images/org/calcurse_br.gif" /&gt;
&lt;p&gt;&lt;a class="reference external" href="https://asciinema.org/a/385766"&gt;Em asciinema.org&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Outra pequena coisa que eu tenho é a configuração &lt;code class="docutils literal"&gt;notification.command&lt;/code&gt; do
calcurse com o seguinte:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;calcurse&lt;span class="w"&gt; &lt;/span&gt;--next&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sed&lt;span class="w"&gt; &lt;/span&gt;-n&lt;span class="w"&gt; &lt;/span&gt;-e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;2s/.*\] \(.*\)/\1/p&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;xargs&lt;span class="w"&gt; &lt;/span&gt;-I&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;{}&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;notify-send&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;  Upcomming appointment&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;{}&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Isso faz com que ele mostre uma notificação no meu sistema algum tempo
(configurável, eu uso 10 minutos) antes de cada compromisso, com sua descrição.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="rotina-usando-python"&gt;
&lt;h2&gt;Rotina usando python&lt;/h2&gt;
&lt;p&gt;O primeiro passo para manter uma rotina é, claro, criá-la.&lt;/p&gt;
&lt;p&gt;Eu queria um jeito simples e fácil de definir e depois editar minha rotina,
então eu a implementei usando dicionários em python, onde a rotina de cada dia
da semana é dada por um dicionário separado.&lt;/p&gt;
&lt;p&gt;A ideia é que a chave define a hora de início de uma ação, e o valor
correspondente define a ação em si. A ação é considerada a atual a partir dessa
hora até a hora da próxima ação. Por exemplo, eu tenho o seguinte dicionário
base para as rotinas:&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="n"&gt;base&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="s1"&gt;'08'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Banho+café&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="s1"&gt;'12'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Almoçar&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="s1"&gt;'15'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Piano&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="s1"&gt;'19'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Jantar&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="s1"&gt;'24'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Dormir&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Se ele fosse usado como uma rotina, significaria que a rotina começa com
&amp;quot;Banho+café&amp;quot; das 8 da manhã até meio-dia, quando mudaria para &amp;quot;Almoçar&amp;quot;, e
assim por diante.&lt;/p&gt;
&lt;p&gt;Como todo dicionário em python, eu posso estendê-lo para implementar a rotina de
um dia:&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="n"&gt;segunda&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;segunda&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="s1"&gt;'09'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Tarefas&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Agora, considerando &lt;code class="docutils literal"&gt;segunda&lt;/code&gt; como a rotina, &amp;quot;Banho+café&amp;quot; só vai até às 9,
quando muda para &amp;quot;Tarefas&amp;quot;, que por sua vez vai até meio-dia, quando &amp;quot;Almoçar&amp;quot;
começa, igual antes, etc.&lt;/p&gt;
&lt;p&gt;Um valor também pode ser deletado, como sempre, usando &lt;code class="docutils literal"&gt;del segunda['09']&lt;/code&gt;,
por exemplo.&lt;/p&gt;
&lt;p&gt;Para definir a minha rotina semanal nesse sistema, eu preciso apenas criar um
dicionário para cada dia da semana usando nomes específicos para as variáveis
(&lt;code class="docutils literal"&gt;segunda&lt;/code&gt;, &lt;code class="docutils literal"&gt;terça&lt;/code&gt;, &lt;code class="docutils literal"&gt;quarta&lt;/code&gt;, &lt;code class="docutils literal"&gt;quinta&lt;/code&gt;, &lt;code class="docutils literal"&gt;sexta&lt;/code&gt;, &lt;code class="docutils literal"&gt;sábado&lt;/code&gt; e
&lt;code class="docutils literal"&gt;domingo&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Eu gosto desse sistema porque eu posso adicionar ações simplesmente adicionando
seu nome e hora de início, e também porque eu posso adicionar ações comuns em
um dicionário base que é estendido pelo dicionário de cada dia, reduzindo
redundância.&lt;/p&gt;
&lt;p&gt;Em seguida, eu tenho um módulo python que sabe como ler cada um dos dicionários
para retornar as informações de interesse:&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;datetime&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;cur_sched&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;&lt;span class="n"&gt;scheds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;cur_sched&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;segunda&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cur_sched&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;terça&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cur_sched&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;quarta&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;          &lt;span class="n"&gt;cur_sched&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;quinta&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cur_sched&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sexta&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cur_sched&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sábado&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;          &lt;span class="n"&gt;cur_sched&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;domingo&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;gran&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;max_minute&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;gran&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;get_cur_wday_time&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;weekday&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;today&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;weekday&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;hour&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;today&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hour&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;minute&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;today&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;minute&lt;/span&gt; &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="n"&gt;gran&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;gran&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;hour&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="n"&gt;hour&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;weekday&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hour&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;minute&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;format_time&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hour&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;minute&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;minute&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="n"&gt;time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;hour&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;02&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;h&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;minute&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;02&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="n"&gt;time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;hour&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;02&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;get_current&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;weekday&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hour&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;minute&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;get_cur_wday_time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;get_sched&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;weekday&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hour&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;minute&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;get_sched&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;weekday&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hour&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;minute&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;minute&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;gran&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;scheds&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;weekday&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;format_time&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hour&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="k"&gt;except&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="k"&gt;pass&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hour&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_minute&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;gran&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;scheds&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;weekday&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;format_time&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="k"&gt;except&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;                &lt;span class="k"&gt;pass&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;get_new&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;weekday&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hour&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;minute&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;get_cur_wday_time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;scheds&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;weekday&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;format_time&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hour&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;minute&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="k"&gt;except&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;&lt;code class="docutils literal"&gt;get_current()&lt;/code&gt; retorna a ação atual da rotina com base na hora e dia atuais.
&lt;code class="docutils literal"&gt;get_new()&lt;/code&gt; faz o mesmo, mas apenas se a ação acabou de começar. Por exemplo,
se &amp;quot;Piano&amp;quot; vai das 3 até às 4 da tarde, e considerando uma granularidade de 30
minutos (que eu estou usando atualmente), ela vai retornar &amp;quot;Piano&amp;quot; apenas entre
3 e 3:30 da tarde.&lt;/p&gt;
&lt;p&gt;Para que eu sempre possa facilmente ver qual é a ação atual com base na minha
rotina, eu tenho um bloco do &lt;a class="reference external" href="https://vivien.github.io/i3blocks/"&gt;i3blocks&lt;/a&gt; específico para isso na barra do meu
sistema:&lt;/p&gt;
&lt;img alt="Barra do sistema mostrando a ação atual da rotina: &amp;quot;Piano&amp;quot;" src="/images/org/i3blocks_sched.png" /&gt;
&lt;p&gt;Ele simplesmente chama o &lt;code class="docutils literal"&gt;get_current()&lt;/code&gt; do módulo python anterior.&lt;/p&gt;
&lt;p&gt;Mas apenas saber a ação atual não é o suficiente, eu preciso ser notificado
quando a ação atual da rotina mudar. É por isso que eu também tenho um &lt;em&gt;job&lt;/em&gt; do
cron que roda a cada 30 minutos e executa a &lt;code class="docutils literal"&gt;get_new()&lt;/code&gt; para checar se a ação
atual da rotina mudou e se sim, me mostra uma notificação:&lt;/p&gt;
&lt;img alt="Notificação mostrando a ação atual com base na rotina: &amp;quot;Schedule change: Piano&amp;quot;" src="/images/org/schedule_notf.png" /&gt;
&lt;p&gt;Por fim, também é útil ver a rotina completa da semana de vez em quando, então
eu também tenho um script que mostra ela no terminal, usando uma cor diferente
para cada ação na rotina, e com as cores definidas aleatoriamente (então elas
mudam a cada execução do programa):&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="ch"&gt;#!/bin/python&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;schedule&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;colored&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;weekday_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;2a&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;3a&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;4a&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;5a&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;6a&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Sáb&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Dom&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;      &amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;weekday&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;weekday_name&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;weekday&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;15&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;get_color&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;hex_num&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;hex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;hex_num6&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hex_num&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;hex_num&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rjust&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'0'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;hex_num6&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'0x'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'#'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;hour&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;minute&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;schedule&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;max_minute&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;schedule&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gran&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;hour&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;minute&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;schedule&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format_time&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hour&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;minute&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;5&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot; &amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;weekday&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;schedule&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_sched&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;weekday&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hour&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;minute&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;                &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;colored&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stylize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;15&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;colored&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;get_color&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;))),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;                      &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;                &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;15&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="extra-tarefas-na-barra-do-sistema"&gt;
&lt;h2&gt;Extra: tarefas na barra do sistema&lt;/h2&gt;
&lt;p&gt;Como eu já uso i3blocks como a barra do meu sistema, eu adicionei também um
bloco com um resumo da situação das minhas de tarefas para me ajudar a ficar
atento a elas e revisá-las regularmente (e não apenas durante a revisão
semanal):&lt;/p&gt;
&lt;img alt="Barra do sistema mostrando um resumo das tarefas" src="/images/org/i3blocks_task.png" /&gt;
&lt;p&gt;O texto no começo mostra o contexto atual, nesse caso, &lt;code class="docutils literal"&gt;sp&lt;/code&gt;. Os três números
seguintes são o número de tarefas &lt;code class="docutils literal"&gt;in&lt;/code&gt; pendentes (em amarelo), o número de
projetos &amp;quot;empacados&amp;quot; (em magenta) e o número de tarefas com prazo final para
essa semana (em vermelho).&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="melhorias-futuras"&gt;
&lt;h2&gt;Melhorias futuras&lt;/h2&gt;
&lt;p&gt;E isso é tudo sobre meu sistema de organização. É basicamente o VIT sobre o
Taskwarrior para organizar minhas tarefas, o calcurse para mostrar meu
calendário, e blocos na barra do sistema e notificações para me ajudar a
acompanhar e para avisar sobre as tarefas, rotina e compromissos.&lt;/p&gt;
&lt;p&gt;Esse sistema funciona bem, mas ainda há melhorias possíveis. Principalmente
integração com o meu celular. Como eu mencionei anteriormente, isso não é um
problema no momento já que estou sempre em casa, mas assim que a pandemia
acabar, eu preciso de um bom jeito de ter minhas tarefas no celular, e
sincronizadas com o meu computador. Eu preciso no mínimo poder facilmente
adicionar tarefas &lt;code class="docutils literal"&gt;in&lt;/code&gt;, e ver meus relatórios, opcionalmente com algum
filtro. Também vou precisar de um calendário com a mesma integração com o
Taskwarrior que eu tenho no computador. Quem sabe com todo o &lt;a class="reference external" href="https://puri.sm/posts/converging-on-convergence-pureos-is-convergent-welcome-to-the-future/"&gt;movimento de
&amp;quot;Convergência&amp;quot; acontecendo na Purism&lt;/a&gt;, eu acabe comprando um Librem 5 e tendo
um sistema bem parecido nos dois dispositivos 😃.&lt;/p&gt;
&lt;/div&gt;
</content><category term="2021"/><category term="gtd"/><category term="organização"/></entry><entry><title>Gerenciando minhas tarefas usando o VIT</title><link href="https://nfraprado.net/pt-br/post/gerenciando-minhas-tarefas-usando-o-vit.html" rel="alternate"/><published>2020-12-22T00:00:00-03:00</published><updated>2020-12-22T00:00:00-03:00</updated><author><name>Nícolas F. R. A. Prado</name></author><id>tag:nfraprado.net,2020-12-22:/pt-br/post/gerenciando-minhas-tarefas-usando-o-vit.html</id><summary type="html">&lt;p&gt;Dois anos atrás eu decidi organizar melhor minha vida. Durante esse tempo eu li
o livro &lt;a class="reference external" href="https://pt.wikipedia.org/wiki/Getting_Things_Done"&gt;Getting Things Done&lt;/a&gt; e descobri o &lt;a class="reference external" href="https://taskwarrior.org/"&gt;Taskwarrior&lt;/a&gt;, um gerenciador de
tarefas para o terminal que não atrapalha.&lt;/p&gt;
&lt;p&gt;Eu apreciei bastante a simplicidade e customizabilidade do Taskwarrior, mas
depois de algum tempo, precisar digitar um …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Dois anos atrás eu decidi organizar melhor minha vida. Durante esse tempo eu li
o livro &lt;a class="reference external" href="https://pt.wikipedia.org/wiki/Getting_Things_Done"&gt;Getting Things Done&lt;/a&gt; e descobri o &lt;a class="reference external" href="https://taskwarrior.org/"&gt;Taskwarrior&lt;/a&gt;, um gerenciador de
tarefas para o terminal que não atrapalha.&lt;/p&gt;
&lt;p&gt;Eu apreciei bastante a simplicidade e customizabilidade do Taskwarrior, mas
depois de algum tempo, precisar digitar um comando para cada ação, como &lt;code class="docutils literal"&gt;task
list&lt;/code&gt; para listar as tarefas, se torna cansativo. Mesmo usando apelidos para
encurtar os comandos, como &lt;code class="docutils literal"&gt;tl&lt;/code&gt; para &lt;code class="docutils literal"&gt;task list&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Buscando uma TUI para o Taskwarrior, eu encontrei o &lt;a class="reference external" href="https://github.com/vit-project/vit/tree/2.x"&gt;VIT&lt;/a&gt;. Eu na verdade só
fiquei no VIT mesmo na segunda tentativa, já que na primeira vez que eu o
encontrei, ele era escrito em Perl e não era uma interface muita boa. Mas com o
lançamento do VIT 2.0 reescrito em python pelo &lt;a class="reference external" href="https://github.com/thehunmonkgroup"&gt;thehunmonkgroup&lt;/a&gt;, ele se tornou a
interface perfeita para o Taskwarrior.&lt;/p&gt;
&lt;p&gt;Antes de eu começar a explicar sobre a minha configuração, tenha em mente que eu
não vou entrar em detalhes sobre o GTD em si, então se você não estiver
familiarizado com ele, talvez dê uma olhada em &lt;a class="reference external" href="https://hamberg.no/gtd"&gt;GTD in 15 minutes&lt;/a&gt;. As coisas
vão fazer mais sentido.&lt;/p&gt;
&lt;div class="section" id="configuracao-do-taskwarrior"&gt;
&lt;h2&gt;Configuração do Taskwarrior&lt;/h2&gt;
&lt;p&gt;Para organizar minhas tarefas seguindo o método GTD, eu preciso adicionar alguns
atributos (chamados &lt;a class="reference external" href="https://taskwarrior.org/docs/udas.html"&gt;UDAs&lt;/a&gt;) e relatórios personalizados no meu arquivo de
configuração do Taskwarrior (&lt;code class="docutils literal"&gt;.taskrc&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;As minhas definições de UDAs são as seguintes:&lt;/p&gt;
&lt;pre class="code ini literal-block"&gt;
&lt;span class="na"&gt;uda.type.label&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;Type&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;uda.type.values&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;in,next,objective,someday,standby,cal&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="na"&gt;uda.priority.values&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;H,L,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;urgency.uda.priority.H.coefficient&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;6.0&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;urgency.uda.priority.L.coefficient&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;-6.0&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="na"&gt;uda.difficulty.type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;string&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;uda.difficulty.label&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;Difficulty&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;uda.difficulty.values&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;H,L,&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Isso adiciona três atributos diferentes. O primeiro e mais importante é o
atributo &lt;code class="docutils literal"&gt;type&lt;/code&gt;. Eu uso ele para atribuir uma tarefa a uma das principais
listas do GTD:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;code class="docutils literal"&gt;in&lt;/code&gt; atribui a tarefa à lista &amp;quot;In&amp;quot;, de entrada, onde eu coleto as tarefas
inicialmente para tirá-las da minha cabeça.&lt;/li&gt;
&lt;li&gt;&lt;code class="docutils literal"&gt;next&lt;/code&gt; a coloca na lista &amp;quot;Next actions&amp;quot;, de próximas ações, onde vivem as
próximas tarefas que eu preciso fazer.&lt;/li&gt;
&lt;li&gt;&lt;code class="docutils literal"&gt;objective&lt;/code&gt; a atribui à lista &amp;quot;Projects&amp;quot;, de projetos, onde cada tarefa
descreve o objetivo de um dos meus projetos atuais, e guia a criação das
tarefas em &amp;quot;Next actions&amp;quot; para cada um dos projetos.&lt;/li&gt;
&lt;li&gt;&lt;code class="docutils literal"&gt;someday&lt;/code&gt; coloca a tarefa na lista &amp;quot;Some day/maybe&amp;quot;. Tarefas colocadas lá
não precisam ser feitas logo, e podem até ser ideias incertas que talvez nunca
sejam feitas. Apesar disso, quando eu tenho certeza de que não quero fazer uma
tarefa, ela é deletada.&lt;/li&gt;
&lt;li&gt;&lt;code class="docutils literal"&gt;standby&lt;/code&gt; a atribui à lista &amp;quot;Waiting for&amp;quot;. É nela que as tarefas que
dependem da ação de outras pessoas ficam esperando.&lt;/li&gt;
&lt;li&gt;&lt;code class="docutils literal"&gt;cal&lt;/code&gt; coloca a tarefa na lista &amp;quot;Calendar&amp;quot;, para que ela apareça no meu
calendário, e com a data configurada.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;O outro atributo é o &lt;code class="docutils literal"&gt;priority&lt;/code&gt;, que eu uso para priorizar tarefas. Sem
prioridade significa prioridade média. &lt;code class="docutils literal"&gt;H&lt;/code&gt; significa prioridade alta, e &lt;code class="docutils literal"&gt;L&lt;/code&gt;,
prioridade baixa, e eles aumentam e diminuem a urgência da tarefa,
respectivamente. O relatório &lt;code class="docutils literal"&gt;next&lt;/code&gt; ordena as tarefas por urgência, então
colocar prioridade alta faz a tarefa aparecer mais alto no relatório, fazendo
com que chame mais minha atenção quando eu estiver passando pelo relatório de
cima para baixo procurando a próxima tarefa para fazer.&lt;/p&gt;
&lt;p&gt;O último atributo é o &lt;code class="docutils literal"&gt;difficulty&lt;/code&gt;, que indica a dificuldade da tarefa, ou
seja, quanta energia me custa para completá-la. Atualmente, eu raramente o uso,
mas a ideia é poder, por exemplo, filtrar o relatório &lt;code class="docutils literal"&gt;next&lt;/code&gt; com apenas as
tarefas com &lt;code class="docutils literal"&gt;difficulty&lt;/code&gt; igual a &lt;code class="docutils literal"&gt;L&lt;/code&gt; (ou seja, as fáceis) sempre que eu
estiver cansado.&lt;/p&gt;
&lt;p&gt;Eu também penso em adicionar uma UDA &lt;code class="docutils literal"&gt;duration&lt;/code&gt; para indicar, também usando
&lt;code class="docutils literal"&gt;H&lt;/code&gt; ou &lt;code class="docutils literal"&gt;L&lt;/code&gt;, se a tarefa gasta mais ou menos tempo do que o normal para
completar, para eu poder filtrar as tarefas com base no tempo que eu tiver
disponível. Mas até o momento eu ainda não senti necessidade de adicioná-la.&lt;/p&gt;
&lt;p&gt;Depois, tem os relatórios:&lt;/p&gt;
&lt;pre class="code ini literal-block"&gt;
&lt;span class="na"&gt;report.next.columns&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;id,start.age,priority,project,tags,recur,scheduled.countdown,due.relative,until.remaining,description.count,urgency&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;report.next.labels&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;ID,Active,P,Project,Tag,Recur,S,Due,Until,Description,Urg&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;report.next.sort&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;urgency-&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="na"&gt;report.all.columns&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;id,status.short,uuid.short,start.active,entry.age,end.age,type,depends.indicator,priority,project,tags.count,recur.indicator,wait.remaining,scheduled.remaining,due,until.remaining,description&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;report.all.labels&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;ID,St,UUID,A,Age,Done,Type,D,P,Project,Tags,R,Wait,Sch,Due,Until,Description&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="na"&gt;report.all_valid.columns&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;id,status.short,uuid.short,start.active,entry.age,end.age,type,depends.indicator,priority,project,tags.count,recur.indicator,wait.remaining,scheduled.remaining,due,until.remaining,description&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;report.all_valid.labels&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;ID,St,UUID,A,Age,Done,Type,D,P,Project,Tags,R,Wait,Sch,Due,Until,Description&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;report.all_valid.filter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;(status:pending or status:waiting)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="na"&gt;report.in.columns&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;id,description&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;report.in.description&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;Inbox&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;report.in.filter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;status:pending limit:page (type:in)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;report.in.labels&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;ID,Description&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="na"&gt;report.someday.columns&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;id,description.count&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;report.someday.description&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;Someday/Maybe&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;report.someday.filter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;limit: type:someday status:pending&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;report.someday.labels&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;ID,Description&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="na"&gt;report.standby.columns&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;id,priority,project,due.relative,description.count,urgency&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;report.standby.description&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;WaitingFor&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;report.standby.labels&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;ID,P,Project,Due,Description,Urgency&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;report.standby.filter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;limit: type:standby status:pending +READY&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;report.standby.sort&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;urgency-&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="na"&gt;report.objectives.columns&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;id,priority,project,description.count,urgency&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;report.objectives.description&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;Projects&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;report.objectives.labels&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;ID,P,Project,Description,Urgency&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;report.objectives.filter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;limit: type:objective status:pending +UNBLOCKED&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;report.objectives.sort&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;urgency-&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="na"&gt;report.type.columns&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;id,description,type&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;report.type.description&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;Type&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;report.type.filter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;status:pending limit:page&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;report.type.labels&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;ID,Description,Type&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="na"&gt;report.cal.columns&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;id,entry.age,recur.indicator,scheduled,due,description&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;report.cal.description&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;Calendar&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;report.cal.filter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;type:cal status:pending limit:page&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;report.cal.labels&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;ID,Age,R,Scheduled,Due,Description&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;report.cal.sort&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;scheduled&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Eu tenho um relatório para cada um dos tipos mencionados para que eu possa ver a
lista de tarefas para cada um deles: &lt;code class="docutils literal"&gt;in&lt;/code&gt;, &lt;code class="docutils literal"&gt;next&lt;/code&gt;, &lt;code class="docutils literal"&gt;objectives&lt;/code&gt; (com um
's'), &lt;code class="docutils literal"&gt;someday&lt;/code&gt;, &lt;code class="docutils literal"&gt;standby&lt;/code&gt; e &lt;code class="docutils literal"&gt;cal&lt;/code&gt;. Além disso, também há o relatório
&lt;code class="docutils literal"&gt;all&lt;/code&gt; que mostra &lt;em&gt;todas&lt;/em&gt; as tarefas, o &lt;code class="docutils literal"&gt;all_valid&lt;/code&gt; que é igual ao &lt;code class="docutils literal"&gt;all&lt;/code&gt;
mas não mostra as tarefas concluídas e deletadas, e o relatório &lt;code class="docutils literal"&gt;type&lt;/code&gt; que
mostra cada uma das tarefas e seu tipo.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="configuracao-do-vit"&gt;
&lt;h2&gt;Configuração do VIT&lt;/h2&gt;
&lt;p&gt;Se configurar o Taskwarrior consiste em configurar os atributos e relatórios
para permitir o meu fluxo de trabalho, configurar o VIT consiste em configurar
os atalhos para tornar esse fluxo o mais fluido possível.&lt;/p&gt;
&lt;p&gt;Os meus atalhos (que são definidos no arquivo &lt;code class="docutils literal"&gt;config.ini&lt;/code&gt; dentro da pasta
&lt;code class="docutils literal"&gt;.vit&lt;/code&gt;) são os seguintes:&lt;/p&gt;
&lt;pre class="code ini literal-block"&gt;
&lt;span class="na"&gt;q&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;{ACTION_QUIT}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="na"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;{ACTION_NOOP}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;aa&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;{ACTION_TASK_ADD}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;ai&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;aatype:in&amp;lt;Space&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;an&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;aatype:next&amp;lt;Space&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;ap&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;aatype:objective project:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;aatype:someday&amp;lt;Space&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;aw&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;aatype:standby&amp;lt;Space&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;ac&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;aatype:cal schedule:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;aN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;aatype:next project:{TASK_PROJECT}&amp;lt;Space&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;aP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;aaproject:{TASK_PROJECT}&amp;lt;Space&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; 

&lt;/span&gt;&lt;span class="na"&gt;gi&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;:in&amp;lt;Enter&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;gn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;:next&amp;lt;Enter&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;gp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;:objectives&amp;lt;Enter&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;gs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;:someday&amp;lt;Enter&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;gw&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;:standby&amp;lt;Enter&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;gc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;:cal&amp;lt;Enter&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;gl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;:list&amp;lt;Enter&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;ga&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;:all_valid&amp;lt;Enter&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;gA&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;:all&amp;lt;Enter&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;gP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;:all_valid project:{TASK_PROJECT}&amp;lt;Enter&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; 

&lt;/span&gt;&lt;span class="na"&gt;M&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;m{TASK_DESCRIPTION}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;S&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;:!r task modify type:someday {TASK_UUID}&amp;lt;Enter&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;W&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;:!r task modify type:standby {TASK_UUID}&amp;lt;Enter&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;P&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;:!r task modify {TASK_UUID} priority:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;F&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;:!r task modify {TASK_UUID} difficulty:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;Y&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;:!r task duplicate {TASK_UUID}&amp;lt;Enter&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c1"&gt;#$ = :!r task sync&amp;lt;Enter&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;zp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;gpf{STUCK_PROJS}&amp;lt;Enter&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;o&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;:! taskopen {TASK_UUID}&amp;lt;Enter&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;C&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;:!r taskwarrior-update_context&amp;lt;Enter&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c1"&gt;# Convenience mappings&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;:1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;:2&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;:3&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;:4&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;:5&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;6&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;:6&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;7&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;:7&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;8&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;:8&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;9&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;:9&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="na"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;m-&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="na"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;m+&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Os atalhos principais são os que começam com &lt;code class="docutils literal"&gt;a&lt;/code&gt; ou &lt;code class="docutils literal"&gt;g&lt;/code&gt;. Esses são os que eu
uso o dia todo. Os que começam com &lt;code class="docutils literal"&gt;a&lt;/code&gt; são para adicionar tarefas, com um
deles para cada tipo, para que eu possa rapidamente adicionar uma tarefa de
qualquer tipo.  Já os que começam com &lt;code class="docutils literal"&gt;g&lt;/code&gt; são para ir para um relatório, então
eu também consigo rapidamente mudar para um relatório específico e ver as
tarefas desse tipo.&lt;/p&gt;
&lt;p&gt;Mas alguns deles são especiais. Por exemplo, &lt;code class="docutils literal"&gt;aN&lt;/code&gt; adiciona uma tarefa do tipo
&lt;code class="docutils literal"&gt;next&lt;/code&gt; e com o projeto igual ao da tarefa atualmente selecionada. Eu uso isso
quando eu estou revisando meus projetos atuais no relatório &lt;code class="docutils literal"&gt;objectives&lt;/code&gt; e
quero adicionar uma tarefa para a próxima ação do projeto que está selecionado.&lt;/p&gt;
&lt;p&gt;&lt;code class="docutils literal"&gt;gP&lt;/code&gt; é outro que eu uso bastante. Ele mostra todas as tarefas com o mesmo
projeto da tarefa atualmente selecionada. Eu uso isso quando estou olhando uma
tarefa de um projeto e quero ver todas as outras tarefas desse projeto, como o
objetivo definido pela tarefa &lt;code class="docutils literal"&gt;objective&lt;/code&gt;, as próximas ações mostradas pelas
tarefas &lt;code class="docutils literal"&gt;next&lt;/code&gt;, se há coisas esperando por outras pessoas em &lt;code class="docutils literal"&gt;standby&lt;/code&gt; ou
algo marcado no calendário em &lt;code class="docutils literal"&gt;cal&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Aí vem alguns atalhos de utilidades usados menos frequentemente. &lt;code class="docutils literal"&gt;M&lt;/code&gt; edita a
descrição da tarefa selecionada. &lt;code class="docutils literal"&gt;S&lt;/code&gt; e &lt;code class="docutils literal"&gt;W&lt;/code&gt; mudam o tipo da tarefa
selecionada para &lt;code class="docutils literal"&gt;someday&lt;/code&gt; e &lt;code class="docutils literal"&gt;standby&lt;/code&gt;, respectivamente. O primeiro é útil
quando eu decido que uma tarefa pode ser feita mais para o futuro ao invés de
agora, enquanto o segundo não é muito usado. &lt;code class="docutils literal"&gt;P&lt;/code&gt; edita a prioridade da tarefa,
enquanto &lt;code class="docutils literal"&gt;F&lt;/code&gt;, sua dificuldade. &lt;code class="docutils literal"&gt;Y&lt;/code&gt; duplica a tarefa selecionada.&lt;/p&gt;
&lt;p&gt;O &lt;code class="docutils literal"&gt;$&lt;/code&gt; que está comentado é usado para sincronizar as tarefas para um servidor
do Taskwarrior central usando o Taskserver. Era essencial para manter as tarefas
sincronizadas entre meu computador e meu celular mesmo quando eu saía. Mas como
ultimamente sair não tem sido uma atividade frequente, fazendo com que eu sempre
esteja no meu computador, eu desabilitei esse atalho por enquanto.&lt;/p&gt;
&lt;p&gt;O atalho &lt;code class="docutils literal"&gt;zp&lt;/code&gt; é um que é um pouco mais complicado, mas &lt;em&gt;muito&lt;/em&gt; útil. Ele vai
para o relatório &lt;code class="docutils literal"&gt;objectives&lt;/code&gt;, que mostra meus projetos atuais, e filtra para
que só os projetos &lt;em&gt;sem&lt;/em&gt; tarefas &lt;code class="docutils literal"&gt;next&lt;/code&gt; sejam mostrados. É importante no GTD
sempre garantir que todos os seus projetos tenham próximas ações atribuídas a
eles, para que o próximo passo para avançá-los seja óbvio. Com esse atalho,
durante minha revisão semanal de todas as tarefas, eu posso facilmente ver quais
projetos não possuem próximas ações e então criar uma para cada projeto usando o
&lt;code class="docutils literal"&gt;aN&lt;/code&gt; já mostrado. Se você estiver se perguntando o que o &lt;code class="docutils literal"&gt;{STUCK_PROJS}&lt;/code&gt;
significa, não se preocupe, eu já vou explicá-lo.&lt;/p&gt;
&lt;p&gt;&lt;code class="docutils literal"&gt;o&lt;/code&gt; usa o &lt;a class="reference external" href="https://github.com/jschlatow/taskopen"&gt;taskopen&lt;/a&gt; na tarefa atualmente selecionada, e esse é outro que eu
uso o tempo todo. O que o taskopen faz é ler as anotações da tarefa e abrir uma
delas (perguntando qual, se houver múltiplas opções). Se a anotação for uma URL,
ela vai ser aberta no navegador de internet. Se for a palavra &amp;quot;Notes&amp;quot;, o
taskopen vai abrir o arquivo de texto associado a essa tarefa (criando ele se
essa for a primeira vez) onde você pode escrever anotações mais compridas. Esses
dois são os que eu conheço e uso o tempo todo. Anotações com texto normal são
ignoradas pelo taskopen.&lt;/p&gt;
&lt;p&gt;O atalho &lt;code class="docutils literal"&gt;C&lt;/code&gt; roda um script para atualizar o contexto atual do Taskwarrior,
mas eu normalmente não preciso rodá-lo já que ele executa automaticamente como
explicado no &lt;a class="reference external" href="/pt-br/post/deteccao-de-contexto-automatica-para-o-taskwarrior.html"&gt;artigo "Detecção de contexto automática para o Taskwarrior"&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Por fim, eu tenho alguns atalhos só por conveniência. Cada um dos dígitos mapeia
para &lt;code class="docutils literal"&gt;:&lt;/code&gt; seguido por esse dígito, para que eu possa pular para uma tarefa
pressionando uma tecla a menos. Mais detalhadamente, normalmente para pular para
a tarefa de id 42 eu precisaria digitar &lt;code class="docutils literal"&gt;:&lt;/code&gt;, &lt;code class="docutils literal"&gt;4&lt;/code&gt;, &lt;code class="docutils literal"&gt;2&lt;/code&gt; e &lt;code class="docutils literal"&gt;&amp;lt;Enter&amp;gt;&lt;/code&gt;. Com
esse atalho, eu não preciso do &lt;code class="docutils literal"&gt;:&lt;/code&gt;, então eu só digito &lt;code class="docutils literal"&gt;4&lt;/code&gt;, &lt;code class="docutils literal"&gt;2&lt;/code&gt; e
&lt;code class="docutils literal"&gt;&amp;lt;Enter&amp;gt;&lt;/code&gt;. Como é muito comum pular para tarefas no VIT, essa uma tecla a
menos vale a pena. Além disso, &lt;code class="docutils literal"&gt;-&lt;/code&gt; e &lt;code class="docutils literal"&gt;+&lt;/code&gt; mapeiam para &lt;code class="docutils literal"&gt;m-&lt;/code&gt; e &lt;code class="docutils literal"&gt;m+&lt;/code&gt;,
respectivamente, sendo que &lt;code class="docutils literal"&gt;m&lt;/code&gt; é o comando padrão para modificar a tarefa,
então para adicionar um rótulo em uma tarefa, por exemplo, eu só preciso
pressionar &lt;code class="docutils literal"&gt;+&lt;/code&gt;, escrever o nome do rótulo e apertar &lt;code class="docutils literal"&gt;&amp;lt;Enter&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;div class="section" id="substituicao-de-variavel-no-vit"&gt;
&lt;h3&gt;Substituição de variável no VIT&lt;/h3&gt;
&lt;p&gt;Você deve ter notado alguns &lt;code class="docutils literal"&gt;{ALGUMA_COISA}&lt;/code&gt; nos atalhos do VIT acima. Eu só
queria dar uma pequena explicação sobre eles (já que uma explicação completa
deve ser lida na documentação do VIT) e também mostrar a minha substituição de
variável personalizada.&lt;/p&gt;
&lt;p&gt;Em primeiro lugar, o &lt;code class="docutils literal"&gt;{ACTION_QUIT}&lt;/code&gt; no atalho &lt;code class="docutils literal"&gt;q&lt;/code&gt; não é uma substituição de
variável, apesar da sintaxe ser parecida (a diferença sendo que ela é a única
coisa depois do &lt;code class="docutils literal"&gt;=&lt;/code&gt;). Isso é só uma das ações do VIT que podem ser mapeadas.
Uma substituição de variável ocorre no atalho &lt;code class="docutils literal"&gt;aN&lt;/code&gt; por exemplo:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="na"&gt;aN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;aatype:next project:{TASK_PROJECT}&amp;lt;Space&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Aqui, &lt;code class="docutils literal"&gt;{TASK_PROJECT}&lt;/code&gt; vai ser substituído pelo atributo de projeto da tarefa
atualmente selecionada. Então é por isso que esse atalho faz o que ele faz. O
&lt;code class="docutils literal"&gt;aa&lt;/code&gt; no começo está mapeado para a ação de adicionar uma nova tarefa, e então
o tipo é colocado como &lt;code class="docutils literal"&gt;next&lt;/code&gt; e o projeto, como o projeto da tarefa
selecionada.  Todos os &lt;code class="docutils literal"&gt;{TASK_*}&lt;/code&gt; são substituições de variável já integradas
do VIT, que podem ser usadas para qualquer um dos atributos das tarefas
(incluindo UDAs).&lt;/p&gt;
&lt;p&gt;Agora, no caso do atalho &lt;code class="docutils literal"&gt;zp&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="na"&gt;zp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;gpf{STUCK_PROJS}&amp;lt;Enter&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class="docutils literal"&gt;{STUCK_PROJS}&lt;/code&gt; é uma substituição de variável personalizada que eu criei. Foi
bem simples, eu só segui o &lt;a class="reference external" href="https://github.com/vit-project/vit/blob/2.x/CUSTOMIZE.md#to-provide-your-own-custom-variable-replacements"&gt;CUSTOMIZE.md do VIT&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Dentro da minha pasta &lt;code class="docutils literal"&gt;.vit&lt;/code&gt;, eu adicionei um &lt;code class="docutils literal"&gt;keybinding/keybinding.py&lt;/code&gt; com
o seguinte:&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;task_proj_stuck&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;get_stuck_proj_ids&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Keybinding&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;replacements&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;_custom_match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;variable&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;variable&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'STUCK_PROJS'&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;variable&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;_custom_replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;arg&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;arg&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'STUCK_PROJS'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s1"&gt;' '&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;get_stuck_proj_ids&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;                &lt;span class="s1"&gt;'match_callback'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;_custom_match&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;                &lt;span class="s1"&gt;'replacement_callback'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;_custom_replace&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;E eu tenho um módulo python &lt;code class="docutils literal"&gt;task_proj_stuck&lt;/code&gt; com o seguinte:&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;tasklib&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TaskWarrior&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;tw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TaskWarrior&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;get_stuck_projects&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot; Get taskwarrior projects that don't have any next actions assigned to
    them.  Next actions here mean actions of type 'next', 'standby' or 'cal',
    either pending or waiting. &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;    &lt;span class="n"&gt;projects&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tw&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pending&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'+READY'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'objective'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;    &lt;span class="n"&gt;next_tasks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tw&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'(status:pending or status:waiting) and type:next'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;standby_tasks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tw&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'(status:pending or status:waiting) and type:standby'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;cal_tasks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tw&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'(status:pending or status:waiting) and type:cal'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;project&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;projects&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="n"&gt;count_next&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;next_tasks&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'project'&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="n"&gt;count_standby&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;standby_tasks&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'project'&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="n"&gt;count_cal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cal_tasks&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'project'&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;count_next&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;count_standby&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;count_cal&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;get_stuck_proj_ids&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'id'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;project&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;get_stuck_projects&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Então o que acontece é que a função &lt;code class="docutils literal"&gt;get_stuck_proj_ids()&lt;/code&gt; retorna um gerador
contendo o id de cada tarefa &lt;code class="docutils literal"&gt;objective&lt;/code&gt; cujo projeto não possui nenhuma
tarefa &lt;code class="docutils literal"&gt;next&lt;/code&gt;. A substituição de variável &lt;code class="docutils literal"&gt;{STUCK_PROJS}&lt;/code&gt; então apenas chama
essa função e junta os ids usando espaço.&lt;/p&gt;
&lt;p&gt;Por exemplo, suponha que existem os projetos &lt;code class="docutils literal"&gt;limpar-quarto&lt;/code&gt; e
&lt;code class="docutils literal"&gt;escrever-artigo-vit&lt;/code&gt;. A tarefa &lt;code class="docutils literal"&gt;objective&lt;/code&gt; do projeto &lt;code class="docutils literal"&gt;limpar-quarto&lt;/code&gt; tem
id 42 e a tarefa do projeto &lt;code class="docutils literal"&gt;escrever-artigo-vit&lt;/code&gt; tem id 99. Ambos os projetos
não tem nenhuma tarefa &lt;code class="docutils literal"&gt;next&lt;/code&gt;, enquanto todos os outros projetos têm. Então,
pressionando &lt;code class="docutils literal"&gt;zp&lt;/code&gt;, o VIT vai executar &lt;code class="docutils literal"&gt;gpf42 99&amp;lt;Enter&amp;gt;&lt;/code&gt;, que vai para o
relatório &lt;code class="docutils literal"&gt;objectives&lt;/code&gt; e filtra apenas pelas tarefas 42 e 99, para que eu
possa focar em adicionar as próximas tarefas para cada um desses projetos
empacados com &lt;code class="docutils literal"&gt;aN&lt;/code&gt;. Bem conveniente, não?&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="hooks-e-taskpirate"&gt;
&lt;h2&gt;&lt;em&gt;Hooks&lt;/em&gt; e taskpirate&lt;/h2&gt;
&lt;p&gt;Outra poderosa funcionalidade do Taskwarrior que permite extensibilidade é a
&lt;a class="reference external" href="https://taskwarrior.org/docs/hooks.html"&gt;API de hooks&lt;/a&gt;. Se você já usou Git, talvez já esteja familiarizado com esse
conceito. Ele permite que um script personalizado execute quando um certo evento
ocorrer no programa, nesse caso, no Taskwarrior.&lt;/p&gt;
&lt;p&gt;Ao invés de criar um &lt;em&gt;hook&lt;/em&gt; diretamente no Taskwarrior, eu decidi usar o
&lt;a class="reference external" href="https://github.com/tbabej/taskpirate"&gt;taskpirate&lt;/a&gt;, que torna as tarefas mais diretamente acessíveis em python. E como
você já deve saber, &lt;a class="reference external" href="https://nfraprado.net/pt-br/tag/python.html"&gt;eu gosto de python&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Atualmente eu tenho um único &lt;em&gt;hook&lt;/em&gt; chamado &lt;code class="docutils literal"&gt;pirate_add_inherit.py&lt;/code&gt;, dentro da
pasta &lt;code class="docutils literal"&gt;hooks&lt;/code&gt;, e o que ele faz é tornar certos atributos herdáveis da tarefa
&lt;code class="docutils literal"&gt;objectives&lt;/code&gt; de um projeto. O código é o seguinte:&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="ch"&gt;#!/bin/python&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;tasklib&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TaskWarrior&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;hook_inherit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'project'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'type'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s1"&gt;'objective'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="n"&gt;tw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TaskWarrior&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/home/nfraprado/.task/data'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="n"&gt;obj_task&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tw&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'objective'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'project'&lt;/span&gt;&lt;span class="p"&gt;])[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="k"&gt;except&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'due'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'priority'&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;obj_task&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;                &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;obj_task&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Como ele é um &lt;em&gt;hook&lt;/em&gt; &lt;code class="docutils literal"&gt;add&lt;/code&gt;, ele executa toda vez que uma nova tarefa é criada.
O que ele faz é o seguinte: sempre que uma tarefa é criada com um projeto, seus
atributos &lt;code class="docutils literal"&gt;due&lt;/code&gt; e &lt;code class="docutils literal"&gt;priority&lt;/code&gt; são colocados iguais aos valores que tiverem na
tarefa &lt;code class="docutils literal"&gt;objective&lt;/code&gt; do projeto, a não ser que eles tenham sido configurados
explicitamente na nova tarefa.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="demonstracao"&gt;
&lt;h2&gt;Demonstração&lt;/h2&gt;
&lt;p&gt;Depois de falar tanto, eu te devo no mínimo alguns gifs mostrando como tudo isso
funciona. Vale dizer que as tarefas mostradas a seguir não são minhas tarefas
reais, caso contrário você veria mais de cem no relatório &lt;code class="docutils literal"&gt;someday&lt;/code&gt;, por
exemplo.&lt;/p&gt;
&lt;p&gt;No primeiro gif, eu mudo para cada um dos relatórios (&lt;code class="docutils literal"&gt;next&lt;/code&gt;, &lt;code class="docutils literal"&gt;in&lt;/code&gt;,
&lt;code class="docutils literal"&gt;standby&lt;/code&gt;, &lt;code class="docutils literal"&gt;objectives&lt;/code&gt; e &lt;code class="docutils literal"&gt;someday&lt;/code&gt;) usando os atalhos &lt;code class="docutils literal"&gt;g&lt;/code&gt;, e então
adiciono uma tarefa &lt;code class="docutils literal"&gt;in&lt;/code&gt; usando &lt;code class="docutils literal"&gt;ai&lt;/code&gt; e em seguida uso &lt;code class="docutils literal"&gt;S&lt;/code&gt; para movê-la
para &lt;code class="docutils literal"&gt;someday&lt;/code&gt;. Eu também uso o atalho padrão &lt;code class="docutils literal"&gt;&amp;lt;Enter&amp;gt;&lt;/code&gt; para inspecionar a
tarefa. Você também pode me ver pulando para tarefas usando usando o id e
pesquisando por uma string usando o atalho padrão &lt;code class="docutils literal"&gt;/&lt;/code&gt;.&lt;/p&gt;
&lt;img alt="Navegação e criação de tarefas no VIT usando meus atalhos personalizados" src="/images/vit/vit1_br.gif" /&gt;
&lt;p&gt;&lt;a class="reference external" href="https://asciinema.org/a/380696"&gt;Em asciinema.org&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Nesse segundo gif, eu uso o atalho padrão &lt;code class="docutils literal"&gt;A&lt;/code&gt; para anotar uma tarefa com um
texto simples e em seguida com a string &amp;quot;Notes&amp;quot;. Então eu uso &lt;code class="docutils literal"&gt;o&lt;/code&gt; para fazer o
taskopen abrir uma nota para a tarefa onde eu escrevo mais anotações.&lt;/p&gt;
&lt;img alt="Anotação de tarefas e uso do taskopen no VIT" src="/images/vit/vit2_br.gif" /&gt;
&lt;p&gt;&lt;a class="reference external" href="https://asciinema.org/a/380697"&gt;Em asciinema.org&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Nesse último gif, eu uso o atalho &lt;code class="docutils literal"&gt;zp&lt;/code&gt; para mostrar apenas os projetos
empacados, e então uso &lt;code class="docutils literal"&gt;aN&lt;/code&gt; em dois deles para criar tarefas &lt;code class="docutils literal"&gt;next&lt;/code&gt;. Por
fim, eu uso &lt;code class="docutils literal"&gt;gP&lt;/code&gt; em uma tarefa para mostrar apenas as tarefas de seu projeto.
Aqui você também pode ver o efeito do &lt;em&gt;hook&lt;/em&gt;, já que a tarefa &lt;code class="docutils literal"&gt;Proxima acao 1&lt;/code&gt;
tem os mesmos valores nos atributos &lt;code class="docutils literal"&gt;priority&lt;/code&gt; e &lt;code class="docutils literal"&gt;due&lt;/code&gt; da tarefa &lt;code class="docutils literal"&gt;Novo
Projeto 1&lt;/code&gt;, sendo que eu não os especifiquei.&lt;/p&gt;
&lt;img alt="Uso dos meus atalhos personalizados zp, aN e gP para facilitar o acompanhamento de projetos no VIT" src="/images/vit/vit3_br.gif" /&gt;
&lt;p&gt;&lt;a class="reference external" href="https://asciinema.org/a/380698"&gt;Em asciinema.org&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;E é isso. Se isso te interessou, dê uma olhada no VIT. Eu apenas mostrei meus
atalhos e meu fluxo de trabalho com ele, mas o VIT é capaz de muito mais.&lt;/p&gt;
&lt;p&gt;Por fim, esse artigo apenas cobriu as coisas relacionadas a gerenciar tarefas e
ao VIT. Ainda há outras partes importantes da minha organização para serem
explicadas, como como eu vejo meu calendário e como eu mantenho uma rotina. Eu
vou falar sobre elas no próximo artigo.&lt;/p&gt;
&lt;/div&gt;
</content><category term="2020"/><category term="vit"/><category term="taskwarrior"/><category term="gtd"/><category term="organização"/></entry><entry><title>Portando um driver de LED de flash para o kernel oficial</title><link href="https://nfraprado.net/pt-br/post/portando-um-driver-de-led-de-flash-para-o-kernel-oficial.html" rel="alternate"/><published>2020-11-20T00:00:00-03:00</published><updated>2020-11-20T00:00:00-03:00</updated><author><name>Nícolas F. R. A. Prado</name></author><id>tag:nfraprado.net,2020-11-20:/pt-br/post/portando-um-driver-de-led-de-flash-para-o-kernel-oficial.html</id><summary type="html">&lt;p&gt;Agora que eu já tinha um cabo serial funcionando para o meu Nexus 5, como
descrito no &lt;a class="reference external" href="/pt-br/post/fazendo-um-cabo-uart-para-o-nexus-5.html"&gt;artigo "Fazendo um cabo UART para o Nexus 5"&lt;/a&gt;, eu estava pronto para a ação em ajudar a
trazer o Nexus 5 para o kernel oficial.&lt;/p&gt;
&lt;p&gt;Olhando &lt;a class="reference external" href="https://masneyb.github.io/nexus-5-upstream/TODO.html"&gt;a página de pendências do Brian …&lt;/a&gt;&lt;/p&gt;</summary><content type="html">&lt;p&gt;Agora que eu já tinha um cabo serial funcionando para o meu Nexus 5, como
descrito no &lt;a class="reference external" href="/pt-br/post/fazendo-um-cabo-uart-para-o-nexus-5.html"&gt;artigo "Fazendo um cabo UART para o Nexus 5"&lt;/a&gt;, eu estava pronto para a ação em ajudar a
trazer o Nexus 5 para o kernel oficial.&lt;/p&gt;
&lt;p&gt;Olhando &lt;a class="reference external" href="https://masneyb.github.io/nexus-5-upstream/TODO.html"&gt;a página de pendências do Brian Masney&lt;/a&gt; haviam algumas opções, mas a
que eu decidi fazer foi a de lanterna traseira. Não tem hardware mais simples
que um LED e seria fácil testar se estava funcionando.&lt;/p&gt;
&lt;p&gt;Mas para a minha surpresa, &lt;a class="reference external" href="https://github.com/AICP/kernel_lge_hammerhead/blob/n7.1/drivers/leds/leds-qpnp.c"&gt;o driver no kernel derivado&lt;/a&gt; (não-oficial) tinha
mais de 3500 linhas! Mas ele não era só para o LED de flash, ele suportava
múltiplos tipos de LED: WLED, Flash/Torch, RGB, MPP e KPDBL.&lt;/p&gt;
&lt;p&gt;Para tornar o porte mais fácil e já que o flash seria o único que eu seria capaz
de testar e ter certeza de que está funcionando, eu decidi criar um novo arquivo
para o driver e copiar só o que precisava para o LED de flash.&lt;/p&gt;
&lt;p&gt;Eu comecei copiando a função &lt;em&gt;probe&lt;/em&gt;, compilando o driver e vendo quais erros de
definições faltando apareciam. Se as definições tivessem &amp;quot;flash&amp;quot; ou &amp;quot;torch&amp;quot; no
nome, eu também copiava elas, se não, eu só removia essas referências. Eu repeti
isso até que eventualmente não haviam mais erros de definições faltando e meu
driver tinha tudo que era necessário para o LED de flash.&lt;/p&gt;
&lt;p&gt;Apesar disso, o código ainda tinha muitos erros de compilação, já que o driver
era para o kernel 3.4 (derivado) e eu estava compilando para o 5.7.6 (oficial).
Então eu fui passando por cada erro, usando as definições em ambos os kernels
como referência, e fazendo as mudanças necessárias.&lt;/p&gt;
&lt;p&gt;Com o driver compilando com sucesso, eu adicionei o CONFIG para ele e o
habilitei como um módulo no &lt;em&gt;defconfig&lt;/em&gt; usado pelo Nexus 5 (&lt;code class="docutils literal"&gt;qcom_defconfig&lt;/code&gt;).
Eu também vasculhei os arquivos de &lt;em&gt;devicetree&lt;/em&gt; na árvore do kernel derivado
para descobrir de quais nós eu precisava para descrever o hardware do LED de
flash para o driver, e as propriedades necessárias neles, para que eu pudesse
adicionar isso no kernel oficial.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Obs&lt;/strong&gt;: Por conta da forma como &lt;em&gt;devicetrees&lt;/em&gt; funcionam, pode ser que
propriedades de um mesmo nó estejam espalhadas por múltiplos arquivos. Só depois
eu descobri que eu poderia compilar o kernel derivado e gerar o fonte da
&lt;em&gt;devicetree&lt;/em&gt; completa a partir do binário com &lt;code class="docutils literal"&gt;dtc -I dtb -O dts -o
downstream_dt.dts
kernel_lge_hammerhead/arch/arm/boot/msm8974-hammerhead-rev-11.dtb&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Tendo um driver que compila, uma &lt;em&gt;devicetree&lt;/em&gt; válida e as configurações
habilitando o driver, eu estava pronto para finalmente compilar meu kernel com o
driver e gravar a imagem no celular. Então eu &lt;a class="reference external" href="https://codeberg.org/nfraprado/linux/commit/d18087e294bb176ee3ffa94f6f82dc60f4b65b63"&gt;fiz um commit com as minhas
alterações&lt;/a&gt; e fui para a batalha.&lt;/p&gt;
&lt;p&gt;E claro que ele falhou. Na verdade, ele falhou tanto que o driver nem se
vinculou ao dispositivo do LED. Já que eu não tinha um bom entendimento de
&lt;em&gt;devicetrees&lt;/em&gt; e de como ocorria a vinculação entre dispositivos e drivers, eu
comecei a pesquisar sobre.&lt;/p&gt;
&lt;p&gt;Um ótimo material que eu encontrei foi &lt;a class="reference external" href="https://elinux.org/images/0/04/Dt_debugging_elce_2015_151006_0421.pdf"&gt;Solving Device Tree Issues&lt;/a&gt; (&lt;a class="reference external" href="https://elinux.org/Device_Tree_frowand"&gt;mais no
eLinux&lt;/a&gt;). Inclusive foi usando o script &lt;code class="docutils literal"&gt;dt_node_info&lt;/code&gt; que essa apresentação
mostra que eu descobri que o dispositivo estava sendo carregado mas o driver
não. Além disso, as técnicas de depuração mostradas, como habilitar prints de
debug dinâmicos nas funções de &lt;em&gt;probe&lt;/em&gt; me ajudaram a descobrir que a função
&lt;em&gt;probe&lt;/em&gt; do meu driver nem estava sendo chamada.&lt;/p&gt;
&lt;p&gt;Depois de ler bastante, tanto documentação online quanto código de outros
drivers, eu reparei que o meu driver estava se registrando no barramento &lt;em&gt;SPMI&lt;/em&gt;,
o que fazia sentido para mim já que ele precisa se comunicar por esse
barramento, mas já que o nó do LED de flash na &lt;em&gt;devicetree&lt;/em&gt; estava sendo
registrado no barramento &lt;em&gt;platform&lt;/em&gt;, meu driver também precisava se registrar
nele, caso contrário eles nunca iriam se vincular. Então isso era uma das coisas
que eu precisava mudar.&lt;/p&gt;
&lt;p&gt;Mas fazer com que meu driver se registrasse no barramento &lt;em&gt;platform&lt;/em&gt;, fazia com
que eu não tivesse mais o ponteiro de &lt;code class="docutils literal"&gt;spmi_device&lt;/code&gt; que eu precisava para usar
as funções de SPMI para ler e escrever nos registradores. Mais uma vez, olhando
nos drivers em volta, como o &lt;code class="docutils literal"&gt;qcom-spmi-iadc&lt;/code&gt;, eu percebi que tinha esse tal
de &lt;em&gt;regmap&lt;/em&gt; que eu poderia usar para ler e escrever nos registradores por SPMI
mas sem ser específico ao SPMI. Eu fiz o sensato e decidi experimentar!&lt;/p&gt;
&lt;p&gt;Com &lt;a class="reference external" href="https://codeberg.org/nfraprado/linux/commit/2fc718b93bf69284e1f174397b1eb5255fd44359"&gt;essas alterações feitas&lt;/a&gt;, o driver agora estava se vinculando ao
dispositivo, mas a função &lt;em&gt;probe&lt;/em&gt; estava falhando com as seguintes mensagens:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="m"&gt;[   14.547394] &lt;/span&gt;&lt;span class="k"&gt;spmi spmi-0:&lt;/span&gt;&lt;span class="gr"&gt; pmic_arb_wait_for_done: transaction failed (0x3)&lt;/span&gt;
&lt;span class="m"&gt;[   14.547405] &lt;/span&gt;&lt;span class="k"&gt;qcom,leds-qpnp fc4cf000.spmi:&lt;/span&gt;pm8941@1:qcom,leds@d300: Unable to read from addr=5, rc(-5)
&lt;span class="m"&gt;[   14.547503] &lt;/span&gt;&lt;span class="k"&gt;qcom,leds-qpnp fc4cf000.spmi:&lt;/span&gt;&lt;span class="gr"&gt;pm8941@1:qcom,leds@d300: Regulator get failed(-517)&lt;/span&gt;
&lt;span class="m"&gt;[   14.547512] &lt;/span&gt;&lt;span class="k"&gt;qcom,leds-qpnp fc4cf000.spmi:&lt;/span&gt;pm8941@1:qcom,leds@d300: Unable to read flash config data
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Já que era o regulador que estava falhando, e dado que eu tinha simplesmente
copiado os nós dos reguladores da &lt;em&gt;devicetree&lt;/em&gt; do kernel derivado, o problema
claramente estava aí no meio. Eu precisava então descobrir quais reguladores que
eram necessários para o LED funcionar, e adicionar eles no kernel oficial caso
ainda não estivessem lá.&lt;/p&gt;
&lt;p&gt;Nesse momento eu enviei um email para o Brian Masney pedindo uma luz, e o
conselho dele foi que eu compilasse e testasse o kernel derivado. Afinal, ter
algo que funciona para servir de referência, mesmo que seja bem desatualizado,
não tem preço.&lt;/p&gt;
&lt;p&gt;Usando uma versão mais antiga das ferramentas de compilação, como &lt;a class="reference external" href="https://github.com/masneyb/nexus-5-upstream/blob/master/build-kernel"&gt;instruído no
script de compilação&lt;/a&gt;, e depois de &lt;a class="reference external" href="https://github.com/masneyb/nexus-5-upstream/pull/6"&gt;um pequeno problema&lt;/a&gt;, eu consegui compilar
o kernel derivado, gravei ele e conferi que o LED e o driver derivado realmente
funcionavam. Eu realmente deveria ter feito isso logo no começo... Já pensou se
o próprio LED de flash estivesse com problema?&lt;/p&gt;
&lt;p&gt;Então eu comecei a explorar o &lt;em&gt;sysfs&lt;/em&gt; desse sistema para entender como ele
funcionava. Eu encontrei o regulador que estava sendo usado pelo LED, cujo
&lt;code class="docutils literal"&gt;status&lt;/code&gt; ia para &lt;code class="docutils literal"&gt;enabled&lt;/code&gt; sempre que eu ligava o LED usando &lt;code class="docutils literal"&gt;echo 1 &amp;gt;
/sys/class/leds/led\:flash_torch/brightness&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Tendo o nó do regulador e a &lt;em&gt;devicetree&lt;/em&gt; no kernel derivado, e o driver do
regulador (&lt;code class="docutils literal"&gt;qcom_spmi-regulator&lt;/code&gt;) e a &lt;em&gt;dtbinding&lt;/em&gt; dele no kernel oficial, eu
comecei a fazer o trabalho de detetive.&lt;/p&gt;
&lt;p&gt;Depois de um pouco de investigação eu finalmente descobri que, já que o endereço
do regulador era &lt;code class="docutils literal"&gt;0xa000&lt;/code&gt;, o regulador chamado &lt;code class="docutils literal"&gt;8941_boost&lt;/code&gt; na árvore
derivada na verdade &lt;a class="reference external" href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=c333dfe8dba7d3e47e97e1cee3c38123e19ae73c"&gt;é conhecido como&lt;/a&gt; &lt;code class="docutils literal"&gt;s4&lt;/code&gt; na árvore oficial, ou também pelo
apelido &lt;code class="docutils literal"&gt;pm8941_5v&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Eu ainda tinha que descobrir a verdadeira identidade do outro regulador, mas
agora que eu já tinha ganhado um pouco de confiança de um trabalho de detetive
bem feito, e com a ajuda de algumas dicas da &lt;em&gt;devicetree&lt;/em&gt;, eu apostei tudo que
que a verdadeira identidade do &lt;code class="docutils literal"&gt;pm8941_chg_boost&lt;/code&gt; era &lt;code class="docutils literal"&gt;5vs1&lt;/code&gt;, também chamado
de &lt;code class="docutils literal"&gt;pm8941_5vs1&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="https://codeberg.org/nfraprado/linux/commit/e3ca691d5b726e64cab869d2dab57b835c14a5b9"&gt;Salvar&lt;/a&gt;, gravar, testar, eeee... nada. Ainda sim não funcionou, mas eu
claramente tinha progredido. Agora a função &lt;em&gt;probe&lt;/em&gt; estava sendo executada com
sucesso, mas as operações de leitura e escrita nos registradores SPMI ainda
estavam falhando:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="m"&gt;[   13.346704] &lt;/span&gt;&lt;span class="k"&gt;leds_qpnp:&lt;/span&gt;qpnp_leds_probe: Probe called!
&lt;span class="m"&gt;[   13.346746] &lt;/span&gt;&lt;span class="k"&gt;spmi spmi-0:&lt;/span&gt;&lt;span class="gr"&gt; pmic_arb_wait_for_done: transaction failed (0x3)&lt;/span&gt;
&lt;span class="m"&gt;[   13.346760] &lt;/span&gt;&lt;span class="k"&gt;qcom,leds-qpnp fc4cf000.spmi:&lt;/span&gt;pm8941@1:qcom,leds@d300: Unable to read from addr=5, rc(-5)
&lt;span class="m"&gt;[   13.347250] &lt;/span&gt;&lt;span class="k"&gt;leds_qpnp:&lt;/span&gt;qpnp_dump_regs: ===== led:flash_0 LED register dump start =====
&lt;span class="m"&gt;[   13.347285] &lt;/span&gt;&lt;span class="k"&gt;leds_qpnp:&lt;/span&gt;qpnp_dump_regs: led:flash_0: 0x40 = 0x0
&lt;span class="m"&gt;[   13.347319] &lt;/span&gt;&lt;span class="k"&gt;leds_qpnp:&lt;/span&gt;qpnp_dump_regs: led:flash_0: 0x41 = 0x0
&lt;span class="m"&gt;[   13.347353] &lt;/span&gt;&lt;span class="k"&gt;leds_qpnp:&lt;/span&gt;qpnp_dump_regs: led:flash_0: 0x42 = 0x0
&lt;span class="m"&gt;[   13.347385] &lt;/span&gt;&lt;span class="k"&gt;leds_qpnp:&lt;/span&gt;qpnp_dump_regs: led:flash_0: 0x43 = 0x0
&lt;span class="m"&gt;[   13.347419] &lt;/span&gt;&lt;span class="k"&gt;leds_qpnp:&lt;/span&gt;qpnp_dump_regs: led:flash_0: 0x44 = 0x0
&lt;span class="m"&gt;[   13.347445] &lt;/span&gt;&lt;span class="k"&gt;leds_qpnp:&lt;/span&gt;qpnp_dump_regs: led:flash_0: 0x48 = 0x0
&lt;span class="m"&gt;[   13.347470] &lt;/span&gt;&lt;span class="k"&gt;leds_qpnp:&lt;/span&gt;qpnp_dump_regs: led:flash_0: 0x49 = 0x0
&lt;span class="m"&gt;[   13.347496] &lt;/span&gt;&lt;span class="k"&gt;leds_qpnp:&lt;/span&gt;qpnp_dump_regs: led:flash_0: 0x4b = 0x0
&lt;span class="m"&gt;[   13.347530] &lt;/span&gt;&lt;span class="k"&gt;leds_qpnp:&lt;/span&gt;qpnp_dump_regs: led:flash_0: 0x4c = 0x0
&lt;span class="m"&gt;[   13.347563] &lt;/span&gt;&lt;span class="k"&gt;leds_qpnp:&lt;/span&gt;qpnp_dump_regs: led:flash_0: 0x4f = 0x0
&lt;span class="m"&gt;[   13.347589] &lt;/span&gt;&lt;span class="k"&gt;leds_qpnp:&lt;/span&gt;qpnp_dump_regs: led:flash_0: 0x46 = 0x0
&lt;span class="m"&gt;[   13.347614] &lt;/span&gt;&lt;span class="k"&gt;leds_qpnp:&lt;/span&gt;qpnp_dump_regs: led:flash_0: 0x47 = 0x0
&lt;span class="m"&gt;[   13.347622] &lt;/span&gt;&lt;span class="k"&gt;leds_qpnp:&lt;/span&gt;qpnp_dump_regs: ===== led:flash_0 LED register dump end =====
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Como eu estava confiante que a &lt;em&gt;devicetree&lt;/em&gt; agora estava certa, eu voltei para o
código do driver. Eu espalhei alguns &lt;code class="docutils literal"&gt;pr_debug()&lt;/code&gt; pela função de &lt;em&gt;probe&lt;/em&gt; e
reparei que o valor do &lt;code class="docutils literal"&gt;reg&lt;/code&gt;, que é lido da &lt;em&gt;devicetree&lt;/em&gt; e usado como o
endereço de base para todas as operações de leitura e escrita, estava com &lt;code class="docutils literal"&gt;0&lt;/code&gt;,
sendo que ele deveria estar com &lt;code class="docutils literal"&gt;0xd300&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Ah. Sério?? Bom, não seria uma aventura completa sem eu adicionar meu próprio
bug, né? 😝&lt;/p&gt;
&lt;p&gt;Depois de &lt;a class="reference external" href="https://codeberg.org/nfraprado/linux/commit/bd120825275df7157078992e6b9f29e8216a53b0"&gt;consertar o bug&lt;/a&gt;, eu recompilei, regravei, reiniciei, retestei
eee... UHUU!!!&lt;/p&gt;
&lt;img alt="Lanterna do Nexus 5 sendo ligada e desligada pela linha de comando" src="/images/qpnp-leds/flash_led.gif" /&gt;
&lt;p&gt;Ele não é liiindo? 😍&lt;/p&gt;
&lt;p&gt;Mas não vamos nos deixar levar por essa luz maravilhosa. Agora que eu finalmente
tinha um driver que de fato funcionava, eu rebaseei tudo em cima do ramo
principal do kernel original (a essa altura o kernel já tinha avançado do 5.7
para o 5.9) para ter certeza de que tudo continuava funcionando e enviei um
&lt;a class="reference external" href="https://lore.kernel.org/linux-arm-msm/20201106165737.1029106-1-nfraprado&amp;#64;protonmail.com/T/"&gt;patch RFC&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;E essa é a lenda de &amp;quot;Como eu portei um driver de LED de flash para o kernel
oficial&amp;quot;! Mas calma, esse é o fim desse artigo, mas não é o fim da aventura.
Ainda tem muitas coisas que eu preciso fazer para conseguir que essa série de
patches adicionando o driver de fato seja incluída no kernel oficial (algumas
delas eu ainda vou descobrir pelas respostas do email).&lt;/p&gt;
&lt;p&gt;Já que meu objetivo aqui era sempre só conseguir fazer funcionar, mudando o
mínimo possível, e só depois limpar e deixar ele decente, é isso que eu vou
precisar começar a fazer agora 🙂.&lt;/p&gt;
&lt;p&gt;Só uma última coisa. Esse artigo pode ter feito parecer que a resolução dos
problemas foi bem direta, mas muito pelo contrário. Teve vários momentos em que
eu não fazia ideia do que fazer, enrolei por várias semanas, e até pensei em
desistir e partir para outro projeto.&lt;/p&gt;
&lt;p&gt;Mas ainda bem que eu sempre continuei tentando e procurando por ajuda, porque
por mais que tenha sido muito frustrante às vezes, também foi super divertido e
eu aprendi demais. Eu não consigo nem expressar minha felicidade no momento que
aquela luz finalmente ligou depois de 4 meses (com pausas) de trabalho.&lt;/p&gt;
&lt;p&gt;E é isso! Espero estar de volta daqui a alguns meses com um novo artigo contando
sobre a minha aventura em transformar um patch RFC em um driver oficial de fato
🙂. Te vejo lá!&lt;/p&gt;
</content><category term="2020"/><category term="nexus5"/><category term="kernel"/></entry><entry><title>Edição de arquivos em massa com ranger</title><link href="https://nfraprado.net/pt-br/post/edicao-de-arquivos-em-massa-com-ranger.html" rel="alternate"/><published>2020-10-20T00:00:00-03:00</published><updated>2020-10-20T00:00:00-03:00</updated><author><name>Nícolas F. R. A. Prado</name></author><id>tag:nfraprado.net,2020-10-20:/pt-br/post/edicao-de-arquivos-em-massa-com-ranger.html</id><summary type="html">&lt;p&gt;Meu gerenciador de arquivos é o &lt;a class="reference external" href="https://ranger.github.io/"&gt;ranger&lt;/a&gt;. Ele é de terminal, permite remapear
todos os comandos me permitindo ser mais eficiente em navegar pelos meus
arquivos, e é incrivelmente extensível já que permite a criação de comandos
customizados em &lt;strong&gt;python&lt;/strong&gt;. Se isso não bastasse, ele também tem um monte de …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Meu gerenciador de arquivos é o &lt;a class="reference external" href="https://ranger.github.io/"&gt;ranger&lt;/a&gt;. Ele é de terminal, permite remapear
todos os comandos me permitindo ser mais eficiente em navegar pelos meus
arquivos, e é incrivelmente extensível já que permite a criação de comandos
customizados em &lt;strong&gt;python&lt;/strong&gt;. Se isso não bastasse, ele também tem um monte de
outras funcionalidades (visualização de arquivos extensível, abas, rótulos,
...). Vá checar a &lt;a class="reference external" href="https://github.com/ranger/ranger"&gt;página do GitHub&lt;/a&gt; deles, sério.&lt;/p&gt;
&lt;p&gt;Uma funcionalidade muito útil do ranger é o comando &lt;code class="docutils literal"&gt;bulkrename&lt;/code&gt;. Ele permite
que você abra um editor com o nome de todos os arquivos selecionados e os edite.
Depois de salvar, um script de shell é gerado para realizar as renomeações
necessárias (e te dá a chance de revisá-lo), e como se fosse mágica &lt;em&gt;*puff*&lt;/em&gt;
você acabou de renomear um monte de arquivos simultaneamente da conveniência do
seu editor de texto preferido.&lt;/p&gt;
&lt;p&gt;Veja um exemplo:&lt;/p&gt;
&lt;img alt="Comando bulkrename do ranger sendo usado para renomear três arquivos de uma só vez" src="/images/ranger-bulk/bulkrename.gif" /&gt;
&lt;p&gt;&lt;a class="reference external" href="https://asciinema.org/a/366010"&gt;Em asciinema.org&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Bem conveniente. Mas se você parar para pensar, é bem limitado. Por que permitir
só renomeação? O mesmo funcionamento poderia ser usado para editar qualquer
atributo do arquivo. Só é necessário fornecer uma forma de obter o atributo para
cada arquivo selecionado e de gerar um comando de shell que mude o atributo para
aplicar a mudança feita pelo usuário.&lt;/p&gt;
&lt;div class="section" id="o-comando-bulk"&gt;
&lt;h2&gt;O comando bulk&lt;/h2&gt;
&lt;p&gt;Recentemente eu quis modificar o rótulo ID3 &lt;em&gt;Artista&lt;/em&gt; de múltiplos arquivos mp3
ao mesmo tempo o que me motivou a escrever a versão genérica do &lt;code class="docutils literal"&gt;bulkrename&lt;/code&gt;
do ranger: &lt;code class="docutils literal"&gt;bulk&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Para isso eu basicamente copiei o código do &lt;code class="docutils literal"&gt;bulkrename&lt;/code&gt; e generalizei a
obtenção do atributo do arquivo e a geração do comando de modificação do
atributo, chamando &lt;code class="docutils literal"&gt;get_attribute()&lt;/code&gt; e &lt;code class="docutils literal"&gt;get_change_attribute_cmd()&lt;/code&gt;,
respectivamente, de um subcomando bulk armazenado no dicionário &lt;code class="docutils literal"&gt;bulk&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;A classe do comando &lt;code class="docutils literal"&gt;bulk&lt;/code&gt; é a seguinte:&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;bulk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Command&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;:bulk &amp;lt;attribute&amp;gt;

    This command opens a list with the attribute &amp;lt;attribute&amp;gt; for each of the
    selected files in an external editor.
    After you edit and save the file, it will generate a shell script
    which changes the attributes in bulk according to the changes you did in the
    file.

    This shell script is opened in an editor for you to review.
    After you close it, it will be executed.
    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;    &lt;span class="n"&gt;bulk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;  &lt;span class="c1"&gt;# pylint: disable=too-many-locals,too-many-statements&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;sys&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;tempfile&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;ranger.container.file&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;ranger.ext.shell_escape&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;shell_escape&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;esc&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="n"&gt;py3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;version_info&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;        &lt;span class="c1"&gt;# get bulk command argument&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="n"&gt;bkname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;        &lt;span class="c1"&gt;# Create and edit the file list&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="n"&gt;files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;thistab&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_selection&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="n"&gt;attributes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bulk&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;bkname&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_attribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="n"&gt;listfile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tempfile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NamedTemporaryFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;delete&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="n"&gt;listpath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;listfile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;py3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="n"&gt;listfile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;utf-8&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="n"&gt;listfile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="n"&gt;listfile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execute_file&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;listpath&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'editor'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="n"&gt;listfile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;listpath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'r'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="n"&gt;new_attributes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;listfile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="n"&gt;listfile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;unlink&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;listpath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_attributes&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;notify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Nothing to be done!&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new_attributes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;        &lt;span class="c1"&gt;# Generate script&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="n"&gt;cmdfile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tempfile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NamedTemporaryFile&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="n"&gt;script_lines&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="n"&gt;script_lines&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;# This file will be executed when you close the editor.&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="n"&gt;script_lines&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;# Please double-check everything, clear the file to abort.&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="n"&gt;script_lines&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bulk&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;bkname&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_change_attribute_cmd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;old&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;                            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;old&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;                            &lt;span class="nb"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_attributes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;old&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="n"&gt;script_content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;script_lines&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;py3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="n"&gt;cmdfile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;script_content&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;utf-8&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="n"&gt;cmdfile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;script_content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="n"&gt;cmdfile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;flush&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;        &lt;span class="c1"&gt;# Open the script and let the user review it&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execute_file&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cmdfile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'editor'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="n"&gt;cmdfile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;seek&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;        &lt;span class="c1"&gt;# Do the attribute changing&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'/bin/sh'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cmdfile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;flags&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'w'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="n"&gt;cmdfile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Então, dentro dessa classe, eu adicionei uma classe para cada um dos subcomandos
bulk que eu queria adicionar: &lt;code class="docutils literal"&gt;id3art&lt;/code&gt;, &lt;code class="docutils literal"&gt;id3tit&lt;/code&gt; e &lt;code class="docutils literal"&gt;id3alb&lt;/code&gt;, que modificam
o rótulo ID3 para o &lt;em&gt;Artista&lt;/em&gt;, &lt;em&gt;Título&lt;/em&gt; e &lt;em&gt;Álbum&lt;/em&gt;, respectivamente.&lt;/p&gt;
&lt;p&gt;Para cada um deles, eu implementei os métodos &lt;code class="docutils literal"&gt;get_attribute()&lt;/code&gt; e
&lt;code class="docutils literal"&gt;get_change_attribute_cmd()&lt;/code&gt;. O método &lt;code class="docutils literal"&gt;get_attribute()&lt;/code&gt; recebe o objeto de
arquivo do ranger e deve retornar uma string com o atributo (no caso do
&lt;code class="docutils literal"&gt;id3art&lt;/code&gt;, o rótulo ID3 &lt;em&gt;Artista&lt;/em&gt;, o qual foi obtido usando o módulo python
&lt;code class="docutils literal"&gt;eyed3&lt;/code&gt;). O método &lt;code class="docutils literal"&gt;get_change_attribute_cmd()&lt;/code&gt; recebe o objeto de arquivo
do ranger, o atributo antigo (o retornado por &lt;code class="docutils literal"&gt;get_attribute()&lt;/code&gt;) e o novo (o
valor editado pelo usuário), e deve retornar uma string contendo o comando de
shell para aplicar a mudança feita pelo usuário (no caso do &lt;code class="docutils literal"&gt;id3art&lt;/code&gt;, &lt;code class="docutils literal"&gt;eyeD3
-a NovoArtista nomeDoArquivo.mp3&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Finalmente, eu também adicionei uma entrada para cada um desses subcomandos no
dicionário &lt;code class="docutils literal"&gt;bulk&lt;/code&gt;, que mapeia o nome do subcomando ao seu objeto.&lt;/p&gt;
&lt;p&gt;Traduzindo tudo isso para código, foi isso o que eu adicionei dentro da classe
&lt;code class="docutils literal"&gt;bulk&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;id3art&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;object&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;get_attribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;eyed3&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="n"&gt;artist&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;eyed3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relative_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;artist&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;artist&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;artist&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;get_change_attribute_cmd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;old&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;ranger.ext.shell_escape&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;shell_escape&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;esc&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;eyeD3 -a &lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;esc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;esc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;id3tit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;object&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;get_attribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;eyed3&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;eyed3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relative_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;get_change_attribute_cmd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;old&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;ranger.ext.shell_escape&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;shell_escape&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;esc&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;eyeD3 -t &lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;esc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;esc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;id3alb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;object&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;get_attribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;eyed3&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="n"&gt;album&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;eyed3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relative_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;album&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;album&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;album&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;get_change_attribute_cmd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;old&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;ranger.ext.shell_escape&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;shell_escape&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;esc&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;eyeD3 -A &lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;esc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;esc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;bulk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;'id3art'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;id3art&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="s1"&gt;'id3tit'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;id3tit&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="s1"&gt;'id3alb'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;id3alb&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;E aqui está ele em ação:&lt;/p&gt;
&lt;img alt="O subcomando bulk id3art sendo usado para mudar o rótulo ID3 Artista de três arquivos mp3 de uma vez só" src="/images/ranger-bulk/bulk.gif" /&gt;
&lt;p&gt;&lt;a class="reference external" href="https://asciinema.org/a/366013"&gt;Em asciinema.org&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Já que eu percebi que esse funcionamento genérico do comando bulk poderia ser
útil para todos os usuários do ranger, cada um implementando seu próprio
subcomando bulk, eu &lt;a class="reference external" href="https://github.com/ranger/ranger/pull/2109"&gt;sugeri a adição da funcionalidade&lt;/a&gt;. Aparentemente a ideia
foi bem aceita, e inclusive há a intenção de tornar o &lt;code class="docutils literal"&gt;bulkrename&lt;/code&gt; apenas um
apelido para um subcomando bulk, então talvez em um futuro próximo você possa
criar seu próprio subcomando bulk usando o comando &lt;code class="docutils literal"&gt;bulk&lt;/code&gt; já integrado ao
ranger 🙂.&lt;/p&gt;
&lt;/div&gt;
</content><category term="2020"/><category term="ranger"/><category term="python"/></entry><entry><title>Geração de listas de reprodução de música com o MPD</title><link href="https://nfraprado.net/pt-br/post/geracao-de-listas-de-reproducao-de-musica-com-o-mpd.html" rel="alternate"/><published>2020-09-20T00:00:00-03:00</published><updated>2020-09-20T00:00:00-03:00</updated><author><name>Nícolas F. R. A. Prado</name></author><id>tag:nfraprado.net,2020-09-20:/pt-br/post/geracao-de-listas-de-reproducao-de-musica-com-o-mpd.html</id><summary type="html">&lt;p&gt;Música é vida. Eu realmente amo ouvir música, apesar que não o mesmo tipo de
música o tempo todo. Mas na maioria das vezes, vai de tudo: Eu gosto de ouvir
qualquer uma das músicas que eu tenho aleatoriamente. Mas quando eu estou
fazendo algo que precisa de concentração (como …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Música é vida. Eu realmente amo ouvir música, apesar que não o mesmo tipo de
música o tempo todo. Mas na maioria das vezes, vai de tudo: Eu gosto de ouvir
qualquer uma das músicas que eu tenho aleatoriamente. Mas quando eu estou
fazendo algo que precisa de concentração (como escrever esse texto) eu só posso
ouvir música &amp;quot;de fundo&amp;quot;, ou seja, música que não tem vocal. Por isso ter
agrupamentos de músicas é bem útil para esse tipo de situação.&lt;/p&gt;
&lt;p&gt;Um jeito de agrupar músicas é por meio de listas de reprodução. Mas os
critérios que determinam quais músicas vão em uma lista é bastante subjetivo já
que é determinado por mim, um ser humano. Foi isso que me motivou a criar algum
jeito de gerar listas de reprodução baseado em critérios variados: nome de
pasta, artista, álbum, etc.&lt;/p&gt;
&lt;p&gt;Recentemente eu li o incrível livro &lt;a class="reference external" href="https://www.amazon.com.br/Python-Fluente-Programa%C3%A7%C3%A3o-Concisa-Eficaz/dp/857522462X"&gt;Python Fluente&lt;/a&gt; (de autor brasileiro!) com
o objetivo de aprofundar minhas habilidades em python, que se tornou minha
linguagem de programação preferida já há algum tempo. Eu aprendi bastante lendo
esse livro, mas ainda não tinha praticado nada do que aprendi. Uma das coisas
mais interessantes que aprendi foi sobre os &lt;a class="reference external" href="https://docs.python.org/3/reference/datamodel.html#special-method-names"&gt;métodos especiais&lt;/a&gt; que fazem dos
objetos em python tão flexíveis. Foi aí que percebi que usando métodos
especiais, eu conseguiria criar objetos de lista de reprodução bem flexíveis e
ao mesmo tempo praticar esse conceito mágico. Ganha-ganha.&lt;/p&gt;
&lt;p&gt;O programa que eu uso para gerenciar minha biblioteca de músicas e também para
tocar as músicas é o &lt;a class="reference external" href="https://www.musicpd.org/"&gt;MPD&lt;/a&gt;. Ele suporta requisição de informações sobre as
músicas por outros programas e também reproduzir músicas com base em uma lista
de reprodução, então foi simples fazer o código de geração de listas de
reprodução usando o MPD.&lt;/p&gt;
&lt;div class="section" id="a-classe-mpdplaylist"&gt;
&lt;h2&gt;A classe MPDPlaylist&lt;/h2&gt;
&lt;p&gt;A ideia é a seguinte: Eu quero poder criar uma lista de reprodução especificando
o que ela deve conter, e também o que ela não deve, e também poder combinar
critérios de outras listas usando operações de E e OU. Isso provavelmente
ficará mais claro mais à frente com os exemplos.&lt;/p&gt;
&lt;p&gt;O código que implementa essa classe está em &lt;code class="docutils literal"&gt;mpd_playlist.py&lt;/code&gt;, e contém o
seguinte:&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;mpd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;MPDClient&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;MPDClient&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timeout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;idletimeout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;localhost&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6600&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;MPDPlaylist&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="vm"&gt;__class__&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;songs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;songs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="nb"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;songs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;songs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;query_songs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="fm"&gt;__repr__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="vm"&gt;__class__&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;(&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;songs&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;)&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="fm"&gt;__or__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;MPDPlaylist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;songs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;union&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;songs&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="fm"&gt;__and__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;MPDPlaylist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;songs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;intersection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;songs&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="fm"&gt;__neg__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;MPDPlaylist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MPDPlaylist&lt;/span&gt;&lt;span class="p"&gt;({})&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;songs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;difference&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;songs&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;query_songs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;queries&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;queries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;queries&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;                &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;                &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;song&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'file'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;song&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;song&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'file'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;song&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;listall&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;                       &lt;span class="n"&gt;song&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'file'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;write_to_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.m3u&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'w'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;                &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;songs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Can't write playlist with no name!&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Aquele trecho com &lt;code class="docutils literal"&gt;MPDClient()&lt;/code&gt; no começo é necessário para conectar ao banco
de dados do MPD para posteriormente obter todas as informações sobre as músicas.
Ele é provido pelo pacote &lt;a class="reference external" href="https://github.com/Mic92/python-mpd2"&gt;python-mpd2&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;O método &lt;code class="docutils literal"&gt;__init__()&lt;/code&gt;, que é chamado quando um novo objeto é criado, pode
receber outra lista de reprodução como seu parâmetro &lt;code class="docutils literal"&gt;query&lt;/code&gt; e nesse caso a
nova lista apenas as músicas da lista passada. Ele também pode receber um
conjunto de músicas (o que eu ainda nem cheguei a usar, mas fazia sentido
suportar). E finalmente, no caso mais comum, pode receber um dicionário contendo
os parâmetros de busca que vão ser usados para filtrar as músicas do MPD. Para
todos os casos, um nome pode opcionalmente ser passado (necessário para que a
lista possa ser salva).&lt;/p&gt;
&lt;p&gt;Então, por exemplo, &lt;code class="docutils literal"&gt;músicas_paramore = MPDPlaylist({'artist': 'Paramore'})&lt;/code&gt;
criaria uma lista de reprodução apenas com músicas do Paramore, e
&lt;code class="docutils literal"&gt;músicas_paramore2 = MPDPlaylist(músicas_paramore)&lt;/code&gt; poderia ser usado para
criar uma cópia dessa lista. Esse segundo caso não parece tão útil mas é a base
para processar expressões, como veremos.&lt;/p&gt;
&lt;p&gt;&lt;code class="docutils literal"&gt;__repr__()&lt;/code&gt; só é usado para depurar. Ele especifica como o objeto é escrito
na tela, nesse caso, mostrando quais músicas a lista de reprodução contém.&lt;/p&gt;
&lt;p&gt;&lt;code class="docutils literal"&gt;__or__()&lt;/code&gt; e &lt;code class="docutils literal"&gt;__and__()&lt;/code&gt; são chamadas quando duas listas de reprodução são
combinadas com OU (usando &lt;code class="docutils literal"&gt;|&lt;/code&gt;) e com E (usando &lt;code class="docutils literal"&gt;&amp;amp;&lt;/code&gt;), retornando uma lista
que contém a união (músicas das duas listas) e a intersecção (apenas músicas
presentes nas duas) respectivamente. &lt;code class="docutils literal"&gt;__neg__()&lt;/code&gt; serve para negar a lista
(usando &lt;code class="docutils literal"&gt;-&lt;/code&gt;), fazendo com que os parâmetros de busca especifiquem o que ela
&lt;strong&gt;não&lt;/strong&gt; deve conter, e portanto que ela contenha tudo menos o que for indicado.&lt;/p&gt;
&lt;p&gt;&lt;code class="docutils literal"&gt;query_songs()&lt;/code&gt; obtém as músicas do MPD com base nos parâmetros de busca e as
salva dentro do objeto.&lt;/p&gt;
&lt;p&gt;&lt;code class="docutils literal"&gt;write_to_file()&lt;/code&gt; salva a lista de reprodução em um arquivo &lt;code class="docutils literal"&gt;.m3u&lt;/code&gt; para que
possa posteriormente ser lido e as músicas reproduzidas pelo MPD. O nome do
arquivo é o que foi fornecido em &lt;code class="docutils literal"&gt;__init__&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Agora, alguns exemplos.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="minhas-listas-de-reproducao"&gt;
&lt;h2&gt;Minhas listas de reprodução&lt;/h2&gt;
&lt;p&gt;Em outro arquivo, &lt;code class="docutils literal"&gt;playlists.py&lt;/code&gt;, eu tenho a definição de todas as minhas
listas de reprodução usando a classe MPDPlaylist:&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;mpd_playlist&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;MPDPlaylist&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;PL&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;saved&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PL&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s1"&gt;'base'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;genres&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;0_Saved&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;buffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PL&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s1"&gt;'base'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;buffer&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;1_Buffer&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;fvt&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PL&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s1"&gt;'file'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;/%&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;1_FvtAlbums&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;br&lt;/span&gt;           &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PL&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s1"&gt;'base'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;genres/br&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;           &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;2_Br&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;classical&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PL&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s1"&gt;'base'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;genres/classical&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;    &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;2_Classical&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;electronic&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PL&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s1"&gt;'base'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;genres/electronic&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;   &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;2_Electronic&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;etc&lt;/span&gt;          &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PL&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s1"&gt;'base'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;genres/etc&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;          &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;2_ETC&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;instrumental&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PL&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s1"&gt;'base'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;genres/instrumental&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;2_Instrumental&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;jazz&lt;/span&gt;         &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PL&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s1"&gt;'base'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;genres/jazz&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;         &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;2_Jazz&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;pop&lt;/span&gt;          &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PL&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s1"&gt;'base'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;genres/pop&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;          &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;2_Pop&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;post_rock&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PL&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s1"&gt;'base'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;genres/post-rock&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;    &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;2_Post-rock&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;rap&lt;/span&gt;          &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PL&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s1"&gt;'base'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;genres/rap&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;          &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;2_Rap&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;rock&lt;/span&gt;         &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PL&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s1"&gt;'base'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;genres/rock&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;         &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;2_Rock&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;soundtrack&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PL&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s1"&gt;'base'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;genres/soundtrack&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;   &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;2_Sountrack&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;common&lt;/span&gt;         &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rock&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;electronic&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;pop&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;post_rock&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;rap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;1_Common&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;not_soundtrack&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;soundtrack&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;2_NotSoundtrack&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;tdg&lt;/span&gt;       &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PL&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s1"&gt;'artist'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Three Days Grace&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;3_TDG&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;minecraft&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PL&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s1"&gt;'base'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;genres/soundtrack/games/%minecraft&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;3_Minecraft&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;lotr&lt;/span&gt;      &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PL&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s1"&gt;'base'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;genres/soundtrack/movies/lotr&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;3_LOTR&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;background&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;classical&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;                &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;jazz&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;                &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;instrumental&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;                &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;PL&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s1"&gt;'artist'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Balmorhea&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;                &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;PL&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s1"&gt;'artist'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Tycho&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;                &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;PL&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s1"&gt;'artist'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;MASTER BOOT RECORD&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;                &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;PL&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s1"&gt;'artist'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;The Album Leaf&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;                &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;PL&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s1"&gt;'base'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;genres/soundtrack/animes/tatami_galaxy/&lt;/span&gt;&lt;span class="si"&gt;%o&lt;/span&gt;&lt;span class="s2"&gt;st&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;                &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;PL&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s1"&gt;'base'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;genres/soundtrack/games&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;                &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;PL&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s1"&gt;'file'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;genres/soundtrack/games/Portal - Still Alive.mp3&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;                &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;PL&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s1"&gt;'file'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;genres/soundtrack/games/portal_2/3-13_want_you_gone.mp3&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;                &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;PL&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s1"&gt;'title'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Here Comes Vi&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;}),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;                &lt;span class="s2"&gt;&amp;quot;1_Background&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;As primeiras listas criadas são:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;code class="docutils literal"&gt;saved&lt;/code&gt;, que contém todas as músicas da pasta &lt;code class="docutils literal"&gt;genres&lt;/code&gt;, essencialmente
minha biblioteca de músicas;&lt;/li&gt;
&lt;li&gt;&lt;code class="docutils literal"&gt;buffer&lt;/code&gt;, com todas as músicas da pasta &lt;code class="docutils literal"&gt;buffer&lt;/code&gt;, que são aquelas que
ainda estou ouvindo para decidir se gosto ou não;&lt;/li&gt;
&lt;li&gt;&lt;code class="docutils literal"&gt;fvt&lt;/code&gt; com todas as músicas de pastas começando com &lt;code class="docutils literal"&gt;%&lt;/code&gt;, que são meus
álbuns favoritos&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Em seguida são definidas listas pra cada um dos gêneros. Algumas delas,
&lt;code class="docutils literal"&gt;rock&lt;/code&gt;, &lt;code class="docutils literal"&gt;electronic&lt;/code&gt;, &lt;code class="docutils literal"&gt;pop&lt;/code&gt;, &lt;code class="docutils literal"&gt;post_rock&lt;/code&gt; e &lt;code class="docutils literal"&gt;rap&lt;/code&gt; são então combinadas
usando o operador OU (&lt;code class="docutils literal"&gt;|&lt;/code&gt;) para criar a lista &lt;code class="docutils literal"&gt;common&lt;/code&gt;. Isso significa que
essa lista de reprodução contém as músicas de todas essas listas combinadas.&lt;/p&gt;
&lt;p&gt;Aí a lista &lt;code class="docutils literal"&gt;not_soundtrack&lt;/code&gt; é criada negando a lista &lt;code class="docutils literal"&gt;soundtrack&lt;/code&gt;, então
aquela contém apenas as músicas não presentes nesta.&lt;/p&gt;
&lt;p&gt;A lista &lt;code class="docutils literal"&gt;tdg&lt;/code&gt; possui apenas músicas do artista &lt;code class="docutils literal"&gt;Three Days Grace&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;A última lista, &lt;code class="docutils literal"&gt;background&lt;/code&gt;, combina várias listas definidas anteriormente
como &lt;code class="docutils literal"&gt;classical&lt;/code&gt;, e também listas anônimas como &lt;code class="docutils literal"&gt;PL({'artist':
&amp;quot;Balmorhea&amp;quot;})&lt;/code&gt; (que são usadas apenas para criar essa lista, e não serão salvas
como listas independentes, portanto não são armazenadas em variáveis e também
não precisam de um nome como parâmetro), bem como remove músicas específicas
como &lt;code class="docutils literal"&gt;PL({'file': &amp;quot;genres/soundtrack/games/Portal - Still Alive.mp3&amp;quot;})&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Não é a sintaxe mais sucinta de todas, mas também não chega a ser extensa e é
bastante flexível: serviu para tudo que eu precisava customizando as minhas
listas.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="salvando-as-listas-de-reproducao"&gt;
&lt;h2&gt;Salvando as listas de reprodução&lt;/h2&gt;
&lt;p&gt;Talvez agora você esteja se perguntando como essas listas de reprodução são
escritas em arquivos se elas são apenas criadas e armazenadas em variáveis. A
resposta é: python é demais 😃. Esse é &lt;code class="docutils literal"&gt;gen_playlists.py&lt;/code&gt;, o código que faz
isso:&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="ch"&gt;#!/bin/python&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;os.path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;expanduser&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;mpd_playlist&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;MPDPlaylist&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;playlists&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;PLD&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;expanduser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;~/.config/mpd/playlists&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c1"&gt;# All variables defined locally with type MPDPlaylist and with a name will be&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;# contained here&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;playlists&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pl&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;pl&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;vars&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;playlists&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;             &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;MPDPlaylist&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;pl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;playlist&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;playlists&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;playlist&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write_to_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PLD&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Como aquele comentário gentil em cima diz, &lt;code class="docutils literal"&gt;playlists = (pl for pl in
vars(playlists).values() if isinstance(pl, MPDPlaylist) and pl.name)&lt;/code&gt; obtém
todas as variáveis do tipo &lt;code class="docutils literal"&gt;MPDPlaylist&lt;/code&gt; de &lt;code class="docutils literal"&gt;playlists.py&lt;/code&gt;, desde que tenham
nome. Aí laço &lt;code class="docutils literal"&gt;for&lt;/code&gt; itera sobre elas e salva cada uma em um arquivo com seu
nome.&lt;/p&gt;
&lt;p&gt;Por fim, eu adicionei uma linha no &lt;code class="docutils literal"&gt;cron&lt;/code&gt; para executar esse script todo
domingo, atualizando minhas listas de reprodução.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="a-lista-de-reproducao-newest"&gt;
&lt;h2&gt;A lista de reprodução 'newest'&lt;/h2&gt;
&lt;p&gt;Como nada é perfeito, tem uma lista de reprodução que eu não consegui integrar
nessa infraestrutura e portanto ficou como um script em bash separado 🤢: a
lista &lt;code class="docutils literal"&gt;newest&lt;/code&gt;. Ela contém as últimas 100 músicas adicionadas à minha
biblioteca.&lt;/p&gt;
&lt;p&gt;Existem quatro tipos de datas em arquivos de acordo com o &lt;code class="docutils literal"&gt;stat&lt;/code&gt;:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;data de criação do arquivo&lt;/li&gt;
&lt;li&gt;data do último acesso&lt;/li&gt;
&lt;li&gt;data da última modificação&lt;/li&gt;
&lt;li&gt;data da última mudança de estado&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Para obter as últimas músicas adicionadas, e não as últimas editadas (às vezes
eu edito os metadados de uma música, e não quero que isso interfira nessa
lista), eu precisava usar a data de criação, mas ela não é suportada pelo MPD,
então é por isso que estou fadado a usar esse script,
&lt;code class="docutils literal"&gt;gen_playlist_newest.sh&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="code shell literal-block"&gt;
&lt;span class="nv"&gt;PLD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;/.config/mpd/playlists&lt;span class="w"&gt;
&lt;/span&gt;find&lt;span class="w"&gt; &lt;/span&gt;~/ark/mus/genres&lt;span class="w"&gt; &lt;/span&gt;-type&lt;span class="w"&gt; &lt;/span&gt;f&lt;span class="w"&gt; &lt;/span&gt;-regextype&lt;span class="w"&gt; &lt;/span&gt;posix-extended&lt;span class="w"&gt; &lt;/span&gt;-regex&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'.*\.(flac|mp3)'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-exec&lt;span class="w"&gt; &lt;/span&gt;stat&lt;span class="w"&gt; &lt;/span&gt;--format&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'%W : %n'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;{}&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\
&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sort&lt;span class="w"&gt; &lt;/span&gt;-nr&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;head&lt;span class="w"&gt; &lt;/span&gt;-n&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;cut&lt;span class="w"&gt; &lt;/span&gt;-d:&lt;span class="w"&gt; &lt;/span&gt;-f2&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sed&lt;span class="w"&gt; &lt;/span&gt;-e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'s|.*mus/\(.*\)|\1|'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\
&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;$PLD&lt;/span&gt;/1_Newest.m3u
&lt;/pre&gt;
&lt;/div&gt;
</content><category term="2020"/><category term="mpd"/><category term="python"/></entry><entry><title>Detecção de contexto automática para o Taskwarrior</title><link href="https://nfraprado.net/pt-br/post/deteccao-de-contexto-automatica-para-o-taskwarrior.html" rel="alternate"/><published>2020-08-24T00:00:00-03:00</published><updated>2020-08-24T00:00:00-03:00</updated><author><name>Nícolas F. R. A. Prado</name></author><id>tag:nfraprado.net,2020-08-24:/pt-br/post/deteccao-de-contexto-automatica-para-o-taskwarrior.html</id><summary type="html">&lt;p&gt;Uma das principais ideias do &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Getting_Things_Done"&gt;GTD&lt;/a&gt; é a associação de contexto à cada tarefa para
que seja muito fácil ver quais tarefas podem ser feitas no contexto atual. Eu
organizo minhas tarefas com o &lt;a class="reference external" href="https://taskwarrior.org/"&gt;Taskwarrior&lt;/a&gt;, então para utilizar contextos com
ele, quando eu adiciono uma nova tarefa eu preciso vinculá-la …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Uma das principais ideias do &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Getting_Things_Done"&gt;GTD&lt;/a&gt; é a associação de contexto à cada tarefa para
que seja muito fácil ver quais tarefas podem ser feitas no contexto atual. Eu
organizo minhas tarefas com o &lt;a class="reference external" href="https://taskwarrior.org/"&gt;Taskwarrior&lt;/a&gt;, então para utilizar contextos com
ele, quando eu adiciono uma nova tarefa eu preciso vinculá-la a um contexto e
também atualizar o contexto sempre que ele mudar. Há diferentes tipos de
contexto, mas os mais fáceis de automatizar são os espaciais, ou seja, quais
tarefas eu posso fazer &lt;strong&gt;onde&lt;/strong&gt; eu estou nesse momento.&lt;/p&gt;
&lt;p&gt;Para vincular uma tarefa a um contexto basta adicionar um rótulo a ela. Como eu
normalmente estou em um de três lugares, eu adiciono o rótulo &lt;code class="docutils literal"&gt;&amp;#64;rep&lt;/code&gt;, &lt;code class="docutils literal"&gt;&amp;#64;sp&lt;/code&gt;
ou &lt;code class="docutils literal"&gt;&amp;#64;uni&lt;/code&gt; dependendo de onde a tarefa pode ser feita.&lt;/p&gt;
&lt;p&gt;Já para atualizar o contexto atual eu precisaria indicar manualmente para o
Taskwarrior onde eu estou no momento toda vez que eu for para um outro lugar.
Por exemplo, eu precisaria digitar &lt;code class="docutils literal"&gt;task context uni&lt;/code&gt; toda vez que eu fosse
para a universidade. Isso além de ser bem chato é uma fonte de erros: já
aconteceu mais de uma vez de eu levar alguns segundos até entender porque
algumas tarefas estavam faltando.&lt;/p&gt;
&lt;p&gt;Mas como todo pequeno problema na vida, isso pode ser solucionado com um pequeno
script. E é por isso que eu escrevi um script em python para detectar e
configurar automaticamente o contexto atual do Taskwarrior.&lt;/p&gt;
&lt;div class="section" id="script-em-python"&gt;
&lt;h2&gt;Script em python&lt;/h2&gt;
&lt;p&gt;A ideia é bem simples: eu quase sempre estou conectado a uma rede sem fio, já
que essa conexão é automática, e cada local tem um nome específico para a rede,
então eu só preciso obter o SSID da rede atual e configurar o contexto
correspondente.&lt;/p&gt;
&lt;p&gt;É isso que o seguinte programa em python faz (além de mandar uma notificação
usando &lt;code class="docutils literal"&gt;notify-send&lt;/code&gt;):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;subprocess&lt;/span&gt;


&lt;span class="n"&gt;contexts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;rep&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;rep wifi 1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;rep wifi 2&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;rep wifi 3&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;sp&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;sp wifi 1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;sp wifi 2&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;uni&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;eduroam&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;get_context&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wifi&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;contexts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;wifi&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;contexts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;get_current_context&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;wifi_cmd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;iwgetid&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;-r&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;capture_output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;wifi&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;wifi_cmd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;get_context&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wifi&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;set_current_context&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;get_current_context&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;notify-send&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Taskwarrior context&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                       &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Setting context to &amp;lt;b&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/b&amp;gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;task&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;context&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;notify-send&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;-u&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;critical&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Taskwarrior context&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                       &lt;span class="s2"&gt;&amp;quot;Failure to detect context&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;task&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;context&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;none&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Obs&lt;/strong&gt;: Os nomes das redes dos contextos &lt;code class="docutils literal"&gt;rep&lt;/code&gt; e &lt;code class="docutils literal"&gt;sp&lt;/code&gt; foram censurados para
evitar exposição.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="servico-do-systemd"&gt;
&lt;h2&gt;Serviço do systemd&lt;/h2&gt;
&lt;p&gt;Já que quando eu vou de um lugar para o outro eu sempre suspendo, hiberno ou
desligo meu computador, esse programa só precisa executar depois de resumir ou
ligar o computador, e é exatamente para isso que serve o seguinte serviço do
systemd:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;[Unit]&lt;/span&gt;
&lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;Set taskwarrior context&lt;/span&gt;
&lt;span class="na"&gt;Wants&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;network-online.target NetworkManager-wait-online.service&lt;/span&gt;
&lt;span class="na"&gt;After&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;network-online.target NetworkManager-wait-online.service hibernate.target suspend.target&lt;/span&gt;

&lt;span class="k"&gt;[Service]&lt;/span&gt;
&lt;span class="na"&gt;User&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;%I&lt;/span&gt;
&lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;oneshot&lt;/span&gt;
&lt;span class="na"&gt;Environment&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;PATH=/usr/bin:/home/nfraprado/ark/code/.path/&lt;/span&gt;
&lt;span class="na"&gt;Environment&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;DISPLAY=:0&lt;/span&gt;
&lt;span class="na"&gt;Environment&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;XAUTHORITY=%h/.Xauthority&lt;/span&gt;
&lt;span class="na"&gt;Environment&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus&lt;/span&gt;
&lt;span class="na"&gt;ExecStartPre&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/usr/bin/sleep 30&lt;/span&gt;
&lt;span class="na"&gt;ExecStart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/home/nfraprado/ark/code/.path/taskwarrior-update_context&lt;/span&gt;
&lt;span class="na"&gt;ExecStartPost&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;

&lt;span class="k"&gt;[Install]&lt;/span&gt;
&lt;span class="na"&gt;WantedBy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;multi-user.target&lt;/span&gt;
&lt;span class="na"&gt;WantedBy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;hibernate.target&lt;/span&gt;
&lt;span class="na"&gt;WantedBy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;suspend.target&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Detalhes sobre esse arquivo de serviço:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Os serviços &lt;code class="docutils literal"&gt;network-online&lt;/code&gt; e &lt;code class="docutils literal"&gt;wait-online&lt;/code&gt; supostamente fazem com que
ele espere o NetworkManager conectar a uma rede antes de executar, mas pelos
meus testes isso não foi o suficiente e portanto eu acabei adicionando 30
segundos de atraso como pode ser visto em &lt;code class="docutils literal"&gt;ExecStartPre&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Os alvos &lt;code class="docutils literal"&gt;hibernate&lt;/code&gt; e &lt;code class="docutils literal"&gt;suspend&lt;/code&gt; fazem com que ele rode depois do
computador resumir ou ligar.&lt;/li&gt;
&lt;li&gt;As variáveis de ambiente &lt;code class="docutils literal"&gt;DISPLAY&lt;/code&gt;, &lt;code class="docutils literal"&gt;XAUTHORITY&lt;/code&gt; e
&lt;code class="docutils literal"&gt;DBUS_SESSION_BUS_ADDRESS&lt;/code&gt; permitem que a notificação apareça.&lt;/li&gt;
&lt;li&gt;&lt;code class="docutils literal"&gt;taskwarrior-update_context&lt;/code&gt; basicamente chama &lt;code class="docutils literal"&gt;set_current_context()&lt;/code&gt; do
script em python.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;E é isso! Com esses dois componentes, depois que eu vou de um lugar para o
outro e abro meu computador, o contexto do Taskwarrior é automaticamente
atualizado, me mostrando só as tarefas relevantes para o lugar que estou
atualmente.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="extra-script-anterior"&gt;
&lt;h2&gt;Extra: Script anterior&lt;/h2&gt;
&lt;p&gt;Uma pequena tangente: antes daquele script em python, eu tinha feito um em bash.
Apesar de executar comandos no bash ser mais limpo do que o &lt;code class="docutils literal"&gt;subprocess.run()&lt;/code&gt;
do python, eu acho a sintaxe do bash horrível. Eu também acho ridículo precisar
definir uma função &lt;code class="docutils literal"&gt;array_contains&lt;/code&gt; (que eu copiei de alguma resposta do
StackOverflow). Enfim, esse era o script em bash caso esteja com curiosidade:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="ch"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="nv"&gt;wifi&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;iwgetid&lt;span class="w"&gt; &lt;/span&gt;-r&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;

&lt;span class="nb"&gt;declare&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-a&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;rep&lt;/span&gt;&lt;span class="o"&gt;=(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;rep wifi 1&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;rep wifi 2&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;rep wifi 3&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;declare&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-a&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;sp&lt;/span&gt;&lt;span class="o"&gt;=(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;sp wifi 1&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;sp wifi 2&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;declare&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-a&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;uni&lt;/span&gt;&lt;span class="o"&gt;=(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;eduroam&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;

array_contains&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;array&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;[@]&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;seeking&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$2&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;element&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="p"&gt;!array&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$element&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$seeking&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;then&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;done&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$in&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

array_contains&lt;span class="w"&gt; &lt;/span&gt;rep&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$wifi&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;context&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;rep
array_contains&lt;span class="w"&gt; &lt;/span&gt;sp&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$wifi&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;context&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;sp
array_contains&lt;span class="w"&gt; &lt;/span&gt;uni&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$wifi&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;context&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;uni

&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-z&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$context&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="k"&gt;then&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;notify-send&lt;span class="w"&gt; &lt;/span&gt;-u&lt;span class="w"&gt; &lt;/span&gt;critical&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Taskwarrior context&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Failure to detect context&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;task&lt;span class="w"&gt; &lt;/span&gt;context&lt;span class="w"&gt; &lt;/span&gt;none&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;/dev/null&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&amp;gt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;notify-send&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Taskwarrior context&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Setting context to &amp;lt;b&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;$context&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/b&amp;gt;&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;task&lt;span class="w"&gt; &lt;/span&gt;context&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$context&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;/dev/null&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&amp;gt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
</content><category term="2020"/><category term="taskwarrior"/><category term="gtd"/><category term="python"/></entry><entry><title>Configurando o mbsync para usar XOAUTH2</title><link href="https://nfraprado.net/pt-br/post/configurando-o-mbsync-para-usar-xoauth2.html" rel="alternate"/><published>2020-07-30T00:00:00-03:00</published><updated>2020-07-30T00:00:00-03:00</updated><author><name>Nícolas F. R. A. Prado</name></author><id>tag:nfraprado.net,2020-07-30:/pt-br/post/configurando-o-mbsync-para-usar-xoauth2.html</id><summary type="html">&lt;p&gt;Por um bom tempo eu usei o &lt;a class="reference external" href="https://www.offlineimap.org/"&gt;offlineimap&lt;/a&gt; para sincronizar meus emails entre os
provedores e o meu computador. Ter acesso a todos os meus emails sem precisar de
internet é bem útil. Mas depois de ver a &lt;a class="reference external" href="https://people.kernel.org/mcgrof/replacing-offlineimap-with-mbsync"&gt;vantagem gigantesca do mbsync em
relação ao offlineimap em termos de performance …&lt;/a&gt;&lt;/p&gt;</summary><content type="html">&lt;p&gt;Por um bom tempo eu usei o &lt;a class="reference external" href="https://www.offlineimap.org/"&gt;offlineimap&lt;/a&gt; para sincronizar meus emails entre os
provedores e o meu computador. Ter acesso a todos os meus emails sem precisar de
internet é bem útil. Mas depois de ver a &lt;a class="reference external" href="https://people.kernel.org/mcgrof/replacing-offlineimap-with-mbsync"&gt;vantagem gigantesca do mbsync em
relação ao offlineimap em termos de performance&lt;/a&gt; e de ter tido problemas de
latência com o offlineimap, eu decidi testar o &lt;a class="reference external" href="https://sourceforge.net/projects/isync/"&gt;mbsync&lt;/a&gt; (o projeto se chama
isync, mas o nome do programa que sincroniza os emails é mbsync).&lt;/p&gt;
&lt;p&gt;O principal problema que eu via em usar o mbsync é que dos três emails que eu
uso atualmente, um deles precisa de autenticação via XOAUTH2. Esse mecanismo é
suportado nativamente no offlineimap, mas não no mbsync.&lt;/p&gt;
&lt;div class="section" id="instalacao"&gt;
&lt;h2&gt;Instalação&lt;/h2&gt;
&lt;p&gt;O primeiro passo obviamente foi instalar o isync/mbsync.&lt;/p&gt;
&lt;p&gt;Em seguida eu instalei uma extensão SASL para o mecanismo XOAUTH2, que no
Arch Linux está disponível no AUR como &lt;a class="reference external" href="https://aur.archlinux.org/packages/cyrus-sasl-xoauth2-git/"&gt;cyrus-sasl-xoauth2-git&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Para que o mbsync consiga autenticar usando XOAUTH2, é necessário um programa
que utilize as credenciais da conta para obter o &lt;em&gt;token&lt;/em&gt; atual. Para isso eu
instalei um pacote python chamado oauth2token, que pode ser instalado do &lt;a class="reference external" href="https://pypi.org/project/oauth2token/"&gt;pip&lt;/a&gt;,
ou diretamente do &lt;a class="reference external" href="https://aur.archlinux.org/packages/oauth2token/"&gt;AUR&lt;/a&gt;. Obrigado ao &lt;a class="reference external" href="https://github.com/VannTen"&gt;VannTen&lt;/a&gt; por criar esse pacote e também por
me ajudar a resolver alguns problemas em configurar o mbsync para usar XOAUTH2!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Obs&lt;/strong&gt;: Quando eu instalei o mbsync originalmente, havia um &lt;a class="reference external" href="https://sourceforge.net/p/isync/bugs/55/"&gt;problema&lt;/a&gt; em usá-lo
com XOAUTH2. Mas no momento de publicação desse artigo isso já foi consertado na
versão 1.3.2.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="configuracao"&gt;
&lt;h2&gt;Configuração&lt;/h2&gt;
&lt;p&gt;Para configurar o oauth2token eu segui as instruções no &lt;a class="reference external" href="https://github.com/VannTen/oauth2token/blob/master/README.rst"&gt;README&lt;/a&gt;, que consistem
em basicamente criar dois arquivos json com as informações da conta
(&lt;code class="docutils literal"&gt;client_id&lt;/code&gt; e &lt;code class="docutils literal"&gt;client_secret&lt;/code&gt; devem ser configurados no provedor, no meu
caso o Gmail) em um diretório específico, executar &lt;code class="docutils literal"&gt;oauth2create &amp;lt;provedor&amp;gt;
&amp;lt;conta&amp;gt;&lt;/code&gt; e logar na conta de email pelo navegador.&lt;/p&gt;
&lt;p&gt;Depois de ter configurado o oauth2token, configurar o mbsync pra usá-lo foi bem
simples. Eu apenas adicionei &lt;code class="docutils literal"&gt;PassCmd &amp;quot;oauth2get &amp;lt;provedor&amp;gt; &amp;lt;conta&amp;gt;&amp;quot;&lt;/code&gt; na seção
&lt;code class="docutils literal"&gt;IMAPAccount&lt;/code&gt; do meu &lt;code class="docutils literal"&gt;.mbsyncrc&lt;/code&gt;, trocando &lt;code class="docutils literal"&gt;&amp;lt;provedor&amp;gt;&lt;/code&gt; e &lt;code class="docutils literal"&gt;&amp;lt;conta&amp;gt;&lt;/code&gt;
pelos valores que eu utilizei no &lt;code class="docutils literal"&gt;oauth2get&lt;/code&gt;, claro. No meu caso não foi
necessário especificar o mecanismo de autenticação nessa mesma seção usando
&lt;code class="docutils literal"&gt;AuthMechs XOAUTH2&lt;/code&gt;, já que XOAUTH2 é considerado o mecanismo mais seguro
instalado, e portanto é o padrão. O que eu precisei fazer foi adicionar
&lt;code class="docutils literal"&gt;AuthMechs PLAIN&lt;/code&gt; na seção &lt;code class="docutils literal"&gt;IMAPAccount&lt;/code&gt; das outras contas que eu não queria
que utilizassem o XOAUTH2.&lt;/p&gt;
&lt;p&gt;Por fim, executei o mbsync para a conta em que eu configurei o XOAUTH2 (&amp;quot;dac&amp;quot;).
A saída verbosa (&lt;code class="docutils literal"&gt;-V&lt;/code&gt;) mostra que ele autenticou usando XOAUTH2 e tudo
funcionou como esperado:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
[nfraprado&amp;#64;ArchWay ~]$ mbsync -V dac
Reading configuration file /home/nfraprado/.mbsyncrc
C: 0/1  B: 0/0  M: +0/0 *0/0 #0/0  S: +0/0 *0/0 #0/0
Channel dac
Opening master store dac-remote...
Resolving imap.gmail.com... ok
Connecting to imap.gmail.com ([2800:3f0:4003:c02::6c]:993)...
Opening slave store dac-local...
Connection is now encrypted
Logging in...
Authenticating with SASL mechanism XOAUTH2...
C: 0/1  B: 0/5  M: +0/0 *0/0 #0/0  S: +0/0 *0/0 #0/0
Opening master box INBOX...
Opening slave box INBOX...
Maildir notice: no UIDVALIDITY, creating new.
Loading master...
master: 0 messages, 0 recent
Loading slave...
slave: 0 messages, 0 recent
Synchronizing...
C: 0/1  B: 1/5  M: +0/0 *0/0 #0/0  S: +0/0 *0/0 #0/0

[ ... ]

Opening master box _Sent...
Opening slave box _Sent...
Creating slave _Sent...
Maildir notice: no UIDVALIDITY, creating new.
Loading master...
Loading slave...
slave: 0 messages, 0 recent
master: 92 messages, 0 recent
Synchronizing...
C: 1/1  B: 5/5  M: +0/0 *0/0 #0/0  S: +367/367 *0/0 #0/0
&lt;/pre&gt;
&lt;/div&gt;
</content><category term="2020"/><category term="xoauth2"/><category term="mbsync"/><category term="email"/></entry><entry><title>Fazendo um cabo UART para o Nexus 5</title><link href="https://nfraprado.net/pt-br/post/fazendo-um-cabo-uart-para-o-nexus-5.html" rel="alternate"/><published>2020-06-30T00:00:00-03:00</published><updated>2020-06-30T00:00:00-03:00</updated><author><name>Nícolas F. R. A. Prado</name></author><id>tag:nfraprado.net,2020-06-30:/pt-br/post/fazendo-um-cabo-uart-para-o-nexus-5.html</id><summary type="html">&lt;p&gt;Recentemente, eu e &lt;a class="reference external" href="https://andrealmeid.com/"&gt;um amigo&lt;/a&gt; começamos a fuçar em como fazer o Nexus 5 rodar o
kernel Linux principal (&lt;a class="reference external" href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git"&gt;diretamente do Linus Torvalds&lt;/a&gt;). O objetivo disso é,
além de ser uma ótima oportunidade de aprendizado, permitir que o Nexus 5 rode
uma distribuição Linux, como o &lt;a class="reference external" href="https://postmarketos.org/"&gt;PostmarketOS&lt;/a&gt;, ao invés de …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Recentemente, eu e &lt;a class="reference external" href="https://andrealmeid.com/"&gt;um amigo&lt;/a&gt; começamos a fuçar em como fazer o Nexus 5 rodar o
kernel Linux principal (&lt;a class="reference external" href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git"&gt;diretamente do Linus Torvalds&lt;/a&gt;). O objetivo disso é,
além de ser uma ótima oportunidade de aprendizado, permitir que o Nexus 5 rode
uma distribuição Linux, como o &lt;a class="reference external" href="https://postmarketos.org/"&gt;PostmarketOS&lt;/a&gt;, ao invés de Android, e ainda
receber atualizações por toda a vida do celular.&lt;/p&gt;
&lt;p&gt;Mas antes de sair programando, primeiro era necessário ter acesso ao UART
(serial) do celular no computador, para que eu pudesse ler as mensagens do
kernel desde o começo da inicialização e descobrir a fonte de qualquer erro que
surgisse. No caso do Nexus 5, o UART fica na saída P2 de áudio, então seria
necessária alguma forma de conectar essa saída no USB do meu computador.&lt;/p&gt;
&lt;p&gt;Eu achei instruções sobre a construção desse cabo na &lt;a class="reference external" href="https://wiki.postmarketos.org/wiki/Serial_debugging:Cable_schematics#Nexus_debug_cable"&gt;wiki do postmarketOS&lt;/a&gt; e no &lt;a class="reference external" href="https://masneyb.github.io/nexus-5-upstream/UART_CABLE.html"&gt;site do nexus-5-upstream&lt;/a&gt;, um projeto do
Brian Masney, que já contribuiu muito código para fazer o Nexus rodar o kernel
principal.&lt;/p&gt;
&lt;p&gt;Mas como as informações nessas duas páginas eram contraditórias, decidi que
seria melhor prototipar o circuito antes e confirmar como funciona, antes de
soldar.&lt;/p&gt;
&lt;div class="section" id="prototipagem"&gt;
&lt;h2&gt;Prototipagem&lt;/h2&gt;
&lt;p&gt;Para o protótipo, eu precisava usar só os componentes que eu já tinha, já que eu
já ia precisar comprar componentes para a placa final e não queria fazer duas
compras.&lt;/p&gt;
&lt;p&gt;Decidi seguir o esquemático da &lt;a class="reference external" href="https://wiki.postmarketos.org/wiki/Serial_debugging:Cable_schematics#Nexus_debug_cable"&gt;wiki do postmarketOS&lt;/a&gt;, já que
ele tinha sido baseado em um esquemático do Google.&lt;/p&gt;
&lt;p&gt;Para o conector P2, eu usei um fone de ouvido velho que estava sobrando, e
cortei o fio, ficando com os fios expostos em uma ponta e o conector na outra.
Para o divisor de tensão no pino TX, eu usei os mesmos valores de resistência do
esquemático, mas usando um resistor de 1kΩ e dois de 100Ω em série para obter o
de 1.2kΩ.&lt;/p&gt;
&lt;p&gt;A conversão entre UART e USB foi feita usando uma placa &lt;a class="reference external" href="https://www.adafruit.com/product/2264"&gt;Adafruit FT232H
breakout&lt;/a&gt; que eu tinha. Já que ela não possui uma saída 3.3V (na minha versão),
eu tentei combinar resistores para dividir os 5V. Consegui chegar em 3.26V, mas
não funcionou.&lt;/p&gt;
&lt;p&gt;Já estava quase desistindo, quando lembrei que o Brian Masney escreveu na página
dele que &lt;a class="reference external" href="https://masneyb.github.io/nexus-5-upstream/UART_CABLE.html"&gt;precisa mesmo ser 3.3V&lt;/a&gt;, e também que eu tinha uma
placa conversora de SPI para USB com uma saída de 3.3V. Era hora de testar.&lt;/p&gt;
&lt;p&gt;Depois de um pequeno problema, que foi resolvido simplesmente mudando a taxa de
transmissão do serial para 115200, funcionou! 🥳&lt;/p&gt;
&lt;p&gt;O protótipo ficou assim:&lt;/p&gt;
&lt;img alt="Protótipo P2 &amp;lt;-&amp;gt; UART &amp;lt;-&amp;gt; USB" src="/images/nexus5-uart/prototype.jpg" /&gt;
&lt;p&gt;Eu sei, horrível, mas funciona!&lt;/p&gt;
&lt;p&gt;Com o protótipo funcionando, eu comprei mais alguns componentes para deixar o
circuito mais atraente.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="placa-final"&gt;
&lt;h2&gt;Placa final&lt;/h2&gt;
&lt;p&gt;Os componentes que eu usei para fazer a placa foram:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;fios&lt;/li&gt;
&lt;li&gt;1 placa padrão (com pelo menos 9x7 furos)&lt;/li&gt;
&lt;li&gt;1 resistor de 1kΩ&lt;/li&gt;
&lt;li&gt;1 resistor de 1.2kΩ&lt;/li&gt;
&lt;li&gt;1 barra de pinos macho 1x7&lt;/li&gt;
&lt;li&gt;1 cabo P2 de 4 vias (de um fone de ouvido velho) Obs: &lt;em&gt;precisa&lt;/em&gt; ser de &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Phone_connector_(audio)#/media/File:3.5mm.jpg"&gt;4
vias&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;1 &lt;a class="reference external" href="https://www.multcomercial.com.br/placa-ftdi-ft232rl-conversor-usb-serial-arduino-gc-54.html"&gt;conversor UART &amp;lt;-&amp;gt; USB&lt;/a&gt; (pode ser qualquer um de 3.3V)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Apenas para referência, os pinos devem ser conectados assim (mas é melhor de
visualizar no &lt;a class="reference external" href="https://wiki.postmarketos.org/wiki/Serial_debugging:Cable_schematics#Nexus_debug_cable"&gt;esquemático do postmarketOS&lt;/a&gt;):&lt;/p&gt;
&lt;table border="1" class="docutils"&gt;
&lt;colgroup&gt;
&lt;col width="20%" /&gt;
&lt;col width="48%" /&gt;
&lt;col width="31%" /&gt;
&lt;/colgroup&gt;
&lt;thead valign="bottom"&gt;
&lt;tr&gt;&lt;th class="head"&gt;Pino de UART&lt;/th&gt;
&lt;th class="head"&gt;Entre&lt;/th&gt;
&lt;th class="head"&gt;Pino do P2&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td&gt;RX&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;Ponta (TX)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;TX (com 3.3V)&lt;/td&gt;
&lt;td&gt;Divisor de tensão (1kΩ e 1.2kΩ)&lt;/td&gt;
&lt;td&gt;Anel 1 (RX com 1.8V)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;GND&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;Anel 2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;3.3V&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;Base&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Eu gastei um pouco de tempo pensando qual seria a melhor posição das conexões e
resistores na placa, e decidi soldar assim:&lt;/p&gt;
&lt;img alt="Conexões na PCB" src="/images/nexus5-uart/lower.jpg" /&gt;
&lt;p&gt;As ligações no verso da placa e pinos correspondentes podem ser vistos a seguir:&lt;/p&gt;
&lt;img alt="Conexões na PCB com indicações" src="/images/nexus5-uart/lower_annotated_br.jpg" /&gt;
&lt;p&gt;Como a PCB era maior que a placa do conversor, eu serrei ela até ficar com mais
ou menos o mesmo tamanho (17x7), mas as conexões ocupam só 9x7.&lt;/p&gt;
&lt;p&gt;Também mudei a posição do jumper da placa conversora para que o VCC ficasse com
3.3V.&lt;/p&gt;
&lt;p&gt;E por fim soldei a barra de pinos na placa conversora e sobre a minha PCB,
resultando em uma placa de 2 andares bem estilosa 😎.&lt;/p&gt;
&lt;p&gt;A placa final ficou assim:&lt;/p&gt;
&lt;img alt="Placa final vista de cima" src="/images/nexus5-uart/final_top.jpg" /&gt;
&lt;img alt="Placa final vista de lado" src="/images/nexus5-uart/final_side.jpg" /&gt;
&lt;/div&gt;
&lt;div class="section" id="testando"&gt;
&lt;h2&gt;Testando&lt;/h2&gt;
&lt;p&gt;Depois de conectar o USB da placa no meu computador e o P2 no Nexus 5, eu abri o
console serial usando o &lt;code class="docutils literal"&gt;picocom&lt;/code&gt; com uma taxa de transmissão de 115200:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;picocom&lt;span class="w"&gt; &lt;/span&gt;/dev/ttyUSB0&lt;span class="w"&gt; &lt;/span&gt;-b&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;115200&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;E liguei o Nexus 5 no modo fastboot segurando os botões de energia e de abaixar
volume, sendo recebido por essa mensagem calorosa:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
welcome to hammerhead bootloader
[10] Power on reason 80
[10] DDR: hynix
[110] Loaded IMGDATA at 0x11000000
[110] Display Init: Start
[190] MDP GDSC already enabled
[190] bpp 24
[230] Config MIPI_CMD_PANEL.
[230] display panel: ORISE
[230] display panel: Default setting
[360] Turn on MIPI_CMD_PANEL.
[410] Display Init: Done
[410] cable type from shared memory: 8
[410] vibe
[610] USB init ept &amp;#64; 0xf96b000
[630] secured device: 1
[630] fastboot_init()
[680] splash: fastboot_op
 FASTBOOT MODE
 PRODUCT_NAME - hammerhead
 VARIANT - hammerhead D821(H) 16GB
 HW VERSION - rev_11
 BOOTLOADER VERSION - HHZ20h
 BASEBAND VERSION - M8974A-2.0.50.2.30
 CARRIER INFO - None
 SERIAL NUMBER - ***
 SIGNING - production
 SECURE BOOT - enabled
 LOCK STATE - unlocked
[790] splash: start
[1820] Fastboot mode started
[1820] udc_start()
&lt;/pre&gt;
&lt;p&gt;Já que eu verifiquei que os pinos do &lt;a class="reference external" href="https://masneyb.github.io/nexus-5-upstream/UART_CABLE.html"&gt;nexus-5-upstream&lt;/a&gt;
estavam errados, mandei uma &lt;a class="reference external" href="https://github.com/masneyb/nexus-5-upstream/pull/4"&gt;mudança de código&lt;/a&gt; consertando, então agora não
deve haver mais confusão 😉.&lt;/p&gt;
&lt;p&gt;Beleza! Agora que eu eu consigo ler todas as mensagens de inicialização, estou
pronto para mergulhar no código do kernel. Apesar que certamente essa foi a
parte mais fácil do projeto 😅...&lt;/p&gt;
&lt;/div&gt;
</content><category term="2020"/><category term="nexus5"/><category term="eletrônica"/></entry><entry><title>Criando listas de filmes e jogos usando Taskwarrior</title><link href="https://nfraprado.net/pt-br/post/criando-listas-de-filmes-e-jogos-usando-taskwarrior.html" rel="alternate"/><published>2020-05-25T00:00:00-03:00</published><updated>2020-05-25T00:00:00-03:00</updated><author><name>Nícolas F. R. A. Prado</name></author><id>tag:nfraprado.net,2020-05-25:/pt-br/post/criando-listas-de-filmes-e-jogos-usando-taskwarrior.html</id><summary type="html">&lt;p&gt;Eu gosto de assistir filmes e jogar jogos, mas também gosto de registrar quais
filmes já assisti e quais jogos já zerei. Fora isso, acabei desenvolvendo o
hábito de dar uma nota para cada filme que assisto.&lt;/p&gt;
&lt;p&gt;Alguns anos atrás eu comecei a tentar me manter mais focado e organizado …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Eu gosto de assistir filmes e jogar jogos, mas também gosto de registrar quais
filmes já assisti e quais jogos já zerei. Fora isso, acabei desenvolvendo o
hábito de dar uma nota para cada filme que assisto.&lt;/p&gt;
&lt;p&gt;Alguns anos atrás eu comecei a tentar me manter mais focado e organizado usando
um gerenciador de lista de afazeres &lt;em&gt;CLI&lt;/em&gt; (com interface de linha de comando)
chamado &lt;a class="reference external" href="https://taskwarrior.org/"&gt;Taskwarrior&lt;/a&gt;. De repente caiu a ficha que eu também poderia usar essa
ferramenta sensacional para registrar essas listas de filmes e jogos!&lt;/p&gt;
&lt;div class="section" id="configuracao-do-taskwarrior"&gt;
&lt;h2&gt;Configuração do Taskwarrior&lt;/h2&gt;
&lt;p&gt;Se você já conhece o Taskwarrior, então você já sabe que tudo que ele precisa é
de um arquivo de configuração (normalmente um &lt;code class="docutils literal"&gt;.taskrc&lt;/code&gt; na sua pasta home), e
uma pasta para manter seus dados.&lt;/p&gt;
&lt;p&gt;Para manter essas listas separadas das minhas tarefas do Taskwarrior, eu decidi
criar uma pasta (&lt;code class="docutils literal"&gt;/home/nfraprado/txt/todo&lt;/code&gt;) separada, com um arquivo de
configuração (&lt;code class="docutils literal"&gt;config&lt;/code&gt;) e uma pasta para os dados (&lt;code class="docutils literal"&gt;data&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Dentro do &lt;code class="docutils literal"&gt;config&lt;/code&gt;, primeiro eu defini a pasta de dados:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="na"&gt;data.location&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;/home/nfraprado/txt/todo/data&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Então removi a cor de tarefas rotuladas e tornei a busca independente de
maiúsculas e minúsculas:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="na"&gt;color.tagged&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;none&lt;/span&gt;

&lt;span class="na"&gt;search.case.sensitive&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;no&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Eu também criei um &lt;em&gt;UDA&lt;/em&gt;, atributo definido pelo usuário, chamado Score, para
que eu pudesse adicionar uma nota para um filme após assisti-lo:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="na"&gt;uda.score.type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;numeric&lt;/span&gt;
&lt;span class="na"&gt;uda.score.label&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;Score&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Finalmente, adicionei os relatórios (&lt;em&gt;reports&lt;/em&gt;) de filmes e de jogos. Cada
relatório pode ter um filtro configurado, determinando quais tarefas serão
exibidas nele, e também suas colunas, determinando quais atributos das tarefas
serão mostrados, além de outras configurações.&lt;/p&gt;
&lt;p&gt;Para os jogos, eu queria dois relatórios: um chamado &lt;code class="docutils literal"&gt;games.todo&lt;/code&gt;, mostrando
os jogos que eu ainda pretendo zerar, e outro chamado &lt;code class="docutils literal"&gt;games.done&lt;/code&gt; com os
jogos que já zerei:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# Games reports&lt;/span&gt;
&lt;span class="na"&gt;report.games.todo.description&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;Games to complete&lt;/span&gt;
&lt;span class="na"&gt;report.games.todo.labels&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;ID,Title&lt;/span&gt;
&lt;span class="na"&gt;report.games.todo.columns&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;id,description&lt;/span&gt;
&lt;span class="na"&gt;report.games.todo.filter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;status:pending limit:page project:games&lt;/span&gt;

&lt;span class="na"&gt;report.games.done.description&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;Games completed&lt;/span&gt;
&lt;span class="na"&gt;report.games.done.labels&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;Title,Completed on&lt;/span&gt;
&lt;span class="na"&gt;report.games.done.columns&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;description,end&lt;/span&gt;
&lt;span class="na"&gt;report.games.done.filter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;status:completed limit:page project:games&lt;/span&gt;
&lt;span class="na"&gt;report.games.done.sort&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;end-&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Já para os filmes, eu também adicionei os relatórios de &lt;code class="docutils literal"&gt;todo&lt;/code&gt; e &lt;code class="docutils literal"&gt;done&lt;/code&gt;, com
a diferença de que o &lt;code class="docutils literal"&gt;done&lt;/code&gt; também possui uma coluna com o &lt;code class="docutils literal"&gt;score&lt;/code&gt; (&lt;em&gt;UDA&lt;/em&gt;
criado anteriormente) para que eu possa ver qual nota dei para cada filme que já
assisti. Além disso, também adicionei um relatório &lt;code class="docutils literal"&gt;movies.rank&lt;/code&gt; que é
ordenado com base na nota, assim posso ter uma lista dos meus filmes favoritos
🙂:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# Movies reports&lt;/span&gt;
&lt;span class="na"&gt;report.movies.todo.description&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;Movies to watch&lt;/span&gt;
&lt;span class="na"&gt;report.movies.todo.labels&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;ID,Tags,Title&lt;/span&gt;
&lt;span class="na"&gt;report.movies.todo.columns&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;id,tags,description&lt;/span&gt;
&lt;span class="na"&gt;report.movies.todo.filter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;status:pending limit:page project:movies&lt;/span&gt;

&lt;span class="na"&gt;report.movies.done.description&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;Movies seen&lt;/span&gt;
&lt;span class="na"&gt;report.movies.done.labels&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;Title,Tags,Score,Watched on&lt;/span&gt;
&lt;span class="na"&gt;report.movies.done.columns&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;description,tags,score,end&lt;/span&gt;
&lt;span class="na"&gt;report.movies.done.filter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;status:completed limit:page project:movies&lt;/span&gt;
&lt;span class="na"&gt;report.movies.done.sort&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;end-&lt;/span&gt;

&lt;span class="na"&gt;report.movies.rank.description&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;Movies ranking&lt;/span&gt;
&lt;span class="na"&gt;report.movies.rank.labels&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;Title,Tags,Score&lt;/span&gt;
&lt;span class="na"&gt;report.movies.rank.columns&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;description,tags,score&lt;/span&gt;
&lt;span class="na"&gt;report.movies.rank.filter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;status:completed limit:page project:movies&lt;/span&gt;
&lt;span class="na"&gt;report.movies.rank.sort&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;score-&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="configuracao-do-bash"&gt;
&lt;h2&gt;Configuração do Bash&lt;/h2&gt;
&lt;p&gt;Com o Taskwarrior configurado, só o que faltava eram alguns atalhos no meu
&lt;code class="docutils literal"&gt;.bashrc&lt;/code&gt; para que fosse mais conveniente adicionar, completar e ver os filmes
e jogos.&lt;/p&gt;
&lt;p&gt;Dentro do meu &lt;code class="docutils literal"&gt;.bashrc&lt;/code&gt;, primeiro adicionei uma variável apontando para o
arquivo de configuração:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nv"&gt;TODOCONFIG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/txt/todo/config&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Então adicionei os atalhos para adicionar, listar e listar completos tanto para
os jogos quanto para os filmes. Também adicionei um para marcar um jogo como
completo e um para ver o ranking de filmes. Em todos esses comandos, usei
&lt;code class="docutils literal"&gt;rc:$TODOCONFIG&lt;/code&gt; para que o Taskwarrior use o arquivo de configuração que
adicionei antes. Para os atalhos de adicionar e marcar como completo, apenas
utilizei os comandos &lt;code class="docutils literal"&gt;add&lt;/code&gt; e &lt;code class="docutils literal"&gt;done&lt;/code&gt; do Taskwarrior, respectivamente. A
listagem é feita simplesmente utilizando os relatórios anteriormente definidos:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# Games list&lt;/span&gt;
&lt;span class="nb"&gt;alias&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;gamadd&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;task rc:$TODOCONFIG add project:games&amp;#39;&lt;/span&gt;
&lt;span class="nb"&gt;alias&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;gamlist&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;task rc:$TODOCONFIG games.todo&amp;#39;&lt;/span&gt;
&lt;span class="nb"&gt;alias&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;gamlistdone&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;task rc:$TODOCONFIG games.done&amp;#39;&lt;/span&gt;
&lt;span class="nb"&gt;alias&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;gamdone&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;task rc:$TODOCONFIG done&amp;#39;&lt;/span&gt;

&lt;span class="c1"&gt;# Movies list&lt;/span&gt;
&lt;span class="nb"&gt;alias&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;movadd&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;task rc:$TODOCONFIG add project:movies&amp;#39;&lt;/span&gt;
&lt;span class="nb"&gt;alias&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;movlist&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;task rc:$TODOCONFIG movies.todo&amp;#39;&lt;/span&gt;
&lt;span class="nb"&gt;alias&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;movlistdone&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;task rc:$TODOCONFIG movies.done&amp;#39;&lt;/span&gt;
&lt;span class="nb"&gt;alias&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;movrank&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;task rc:$TODOCONFIG movies.rank&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;O atalho para completar um filme não pode ser feito diretamente, já que eu
também quero poder dar uma nota ao completá-lo. Por isso criei uma simples
função em Bash que recebe o ID da tarefa e a nota, aplica a nota dada na tarefa
e a marca como completa:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;movdone&lt;span class="o"&gt;()&lt;/span&gt;
&lt;span class="o"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-z&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-z&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$2&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="k"&gt;then&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Usage: movdone id score&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;task&lt;span class="w"&gt; &lt;/span&gt;rc:&lt;span class="nv"&gt;$TODOCONFIG&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;modify&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;score:&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$2&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;task&lt;span class="w"&gt; &lt;/span&gt;rc:&lt;span class="nv"&gt;$TODOCONFIG&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;done&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="resultado"&gt;
&lt;h2&gt;Resultado&lt;/h2&gt;
&lt;p&gt;Com toda essa configuração feita, eu finalmente podia registrar meus filmes e
jogos facilmente.&lt;/p&gt;
&lt;p&gt;Adicionar um novo filme para assistir é tão simples quanto um &lt;code class="docutils literal"&gt;movadd
nome_do_filme&lt;/code&gt; (espaços no nome do filme podem ser usados sem problema).&lt;/p&gt;
&lt;p&gt;E depois de assistir um filme, posso marcá-lo como finalizado e dar uma nota com
um &lt;code class="docutils literal"&gt;movdone id nota&lt;/code&gt;. O &lt;code class="docutils literal"&gt;id&lt;/code&gt; de um filme pode ser obtido usando &lt;code class="docutils literal"&gt;movlist&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Esses são os filmes que eu planejo assistir:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
[nfraprado&amp;#64;ArchWay ~]$ movlist
Using alternate .taskrc file /home/nfraprado/txt/todo/config

ID Title
23 Once Upon a Time In Hollywood
27 Ready player one
28 In The Shadow Of The Moon 2007
&lt;/pre&gt;
&lt;p&gt;Os últimos 5 filmes que assisti:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
[nfraprado&amp;#64;ArchWay ~]$ movlistdone
Using alternate .taskrc file /home/nfraprado/txt/todo/config

Title                                             Tags   Score Watched on
Arrival                                                      8 2020-05-25
The Green Mile                                               8 2020-05-17
The Boy Who Harnessed the Wind                               8 2020-04-25
Jojo Rabbit                                                  6 2020-04-18
Back to the Future                                           8 2020-04-12
&lt;/pre&gt;
&lt;p&gt;Os filmes que dei a maior nota (desde que criei essa lista):&lt;/p&gt;
&lt;pre class="literal-block"&gt;
[nfraprado&amp;#64;ArchWay ~]$ movrank
Using alternate .taskrc file /home/nfraprado/txt/todo/config

Title                                             Tags   Score
The Lord of the Rings: The Return of the King               10
The Lord of the Rings: The Two Towers                       10
The Lord of the Rings: The Fellowship of the Ring           10
Whisper of the Heart                              ghibli     9
Apollo 11                                         doc        9
Life is beautiful                                            9
Koe no katachi                                               9
&lt;/pre&gt;
&lt;p&gt;Alguns jogos que pretendo zerar:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
[nfraprado&amp;#64;ArchWay ~]$ gamlist
Using alternate .taskrc file /home/nfraprado/txt/todo/config

ID Title
 6 FTL
 7 The Witness
 8 Factorio
 9 Stardew Valley
10 Half Life
&lt;/pre&gt;
&lt;p&gt;E os últimos 5 jogos que eu zerei:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
[nfraprado&amp;#64;ArchWay ~]$ gamlistdone
Using alternate .taskrc file /home/nfraprado/txt/todo/config

Title                                                      Completed on
The Stanley Parable                                        2020-04-24
Undertale                                                  2020-04-17
Cave Story                                                 2020-03-30
Antichamber                                                2020-03-22
Papers, Please                                             2020-03-19
&lt;/pre&gt;
&lt;/div&gt;
</content><category term="2020"/><category term="cli"/><category term="taskwarrior"/><category term="organização"/></entry></feed>